USD ($)
$
United States Dollar
Euro Member Countries
India Rupee

CRUD Operations via API Endpoints with Relationships

Lesson 11/30 | Study Time: 25 Min

CRUD operations via API endpoints with relationships refer to building backend APIs that allow clients to create, read, update, and delete data across multiple related entities in a database. These APIs are commonly implemented using RESTful principles and an ORM to manage relationships such as one-to-one, one-to-many, and many-to-many. By exposing structured endpoints, applications can maintain data integrity while enabling efficient interaction between interconnected resources.

Understanding Relationships

Relationships in databases link tables to model real-world connections, such as one-to-many (one user has many posts) or many-to-many (posts have many tags, tags belong to many posts). These require special handling in API endpoints to avoid data inconsistencies or N+1 query problems, where fetching related data triggers excessive database calls. Proper design uses ORM features like SQLAlchemy's relationship() for automatic joins.

Key relationship types include


Defining Models with Relationships

Start by modeling entities with SQLAlchemy or similar ORMs, using declarative_base() for tables and relationship() for links. For a blog app, define User and Post models where one user owns many posts. Always index foreign keys for performance.

Example models in Python using SQLAlchemy

text
from sqlalchemy import Column, Integer, String, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True)
name = Column(String)
posts = relationship("Post", back_populates="owner")

class Post(Base):
__tablename__ = "posts"
id = Column(Integer, primary_key=True)
title = Column(String)
user_id = Column(Integer, ForeignKey("users.id"))
owner = relationship("User", back_populates="posts")

This setup enables eager loading via joinedload() to fetch related data efficiently.

Basic CRUD Without Relationships

Implement standalone CRUD first to establish patterns: POST for create, GET for read (list/single), PUT/PATCH for update, DELETE for delete. Use Pydantic schemas for validation and FastAPI's dependency injection for database sessions. Pagination via query params prevents overload.

Standard endpoint patterns follow REST conventions

CRUD with One-to-Many Relationships

For one-to-many, expose related data in reads via nested serialization and handle creation/updates carefully to maintain referential integrity. Use session.add() for new related objects, ensuring foreign keys link correctly. Best practice: Cascade deletes via ORM configuration.

Creating Related Resources

To create a post for a user:


1. Fetch the user by ID.

2. Assign post.owner = user.

3. session.add(post); session.commit().


FastAPI example

text
@app.post("/posts/")
def create_post(post: PostCreate, db: Session = Depends(get_db)):
user = db.get(User, post.user_id)
if not user:
raise HTTPException(404, "User not found")
db_post = Post(**post.dict(), owner=user)
db.add(db_post)
db.commit()
db.refresh(db_post)
return db_post

This validates user existence before linking.

Reading with Nested Data

Fetch posts with user details using selectinload()

text
@app.get("/posts/{post_id}")
def read_post(post_id: int, db: Session = Depends(get_db)):
post = db.scalars(
select(Post).options(selectinload(Post.owner)).where(Post.id == post_id)
).first()
if not post:
raise HTTPException(404, "Post not found")
return post

Response includes nested owner: {"id":1, "title":"My Post", "owner":{"id":1, "name":"Alice"}}.

Handling Many-to-Many Relationships

Many-to-many uses junction tables; ORMs auto-manage them via secondary arguments in relationship(). Endpoints often separate association management, like /posts/{id}/tags/ for adding tags. Avoid deep nesting to prevent complex payloads.

Example model addition

text
tags = Table('post_tags', Base.metadata,
Column('post_id', Integer, ForeignKey('posts.id')),
Column('tag_id', Integer, ForeignKey('tags.id'))
)

class Post(Base):
tags = relationship("Tag", secondary=tags, back_populates="posts")


Update endpoint for associations

text
@app.post("/posts/{post_id}/tags/{tag_id}")
def add_tag(post_id: int, tag_id: int, db: Session = Depends(get_db)):
post = db.get(Post, post_id)
tag = db.get(Tag, tag_id)
post.tags.append(tag)
db.commit()
return {"message": "Tag added"}

This treats the relationship as a sub-resource.

Best Practices and Error Handling

Follow RESTful standards: Use HTTP status codes (201 Created, 404 Not Found) and consistent naming. Implement transactions for atomicity in related updates. Secure with authentication to prevent unauthorized deletes.

Common pitfalls and solutions


1. N+1 queries: Use eager loading (joinedload/selectinload).
​2. Partial updates: Prefer PATCH with optional fields via Pydantic's exclude_unset=True.
​3. Validation: Custom validators for foreign key existence.
4. Pagination: Always include limit/offset for lists.

Error handling example

text
if not post:
raise HTTPException(status_code=404, detail="Post not found")[page:1]
Testing Relationship Endpoints

Test comprehensively with tools like Postman or FastAPI's /docs. Verify cascades (deleting user removes posts) and edge cases (orphaned relations). Use pytest with TestClient for automation.

Sample curl tests

text
# Create user then post
curl -X POST "http://localhost:8000/users/" -d '{"name":"Alice"}'
curl -X POST "http://localhost:8000/posts/" -d '{"title":"Test","user_id":1}'

# Read with relations
curl "http://localhost:8000/posts/1"
himanshu singh

himanshu singh

Product Designer
Profile

Class Sessions

1- HTTP Methods and REST Principles 2- Status Codes, Headers, and Request/Response cycles 3- JSON and XML Data Formats for API Payloads 4- Resource Naming Conventions and URI Design Best Practices 5- Statelessness, HATEOAS, and API Versioning Strategies 6- Rate Limiting, Caching, and Idempotency for Scalability 7- FastAPI Setup, Pydantic Models, and Async Endpoint Creation 8- Path/Query Parameters, Request/Response Validation 9- Dependency Injection and Middleware for Authentication/Authorization 10- SQLAlchemy ORM with Async Support for PostgreSQL/MySQL 11- CRUD Operations via API Endpoints with Relationships 12- Database Migrations Using Alembic and Connection Pooling 13- JWT/OAuth2 Implementation with FastAPI Security Utilities 14- File Uploads, Pagination, and Real-Time WebSockets 15- Input Sanitization, CORS, and OWASP Top 10 Defenses 16- Unit/integration testing with Pytest and FastAPI TestClient 17- API Documentation Generation with OpenAPI/Swagger 18- Mocking External Services and Load Testing with Locust 19- Containerization with Docker and Orchestration via Docker Compose 20- Deployment to Cloud Platforms 21- CI/CD Pipelines Using GitHub Actions and Monitoring with Prometheus 22- Consuming APIs in React/Vue.js with Axios/Fetch 23- State Management (Redux/Zustand) for API Data Flows 24- Error Handling, Optimistic Updates, and Frontend Caching Strategies 25- Async Processing with Celery/Redis for Background Tasks 26- Caching Layers (Redis) and Database Query Optimization 27- Microservices Patterns and API Gateways 28- Building a Full-Stack CRUD App with User Auth and File Handling 29- API Analytics, Logging (Structlog), and Error Tracking 30- Code Reviews, Maintainability, and Evolving APIs in Production

Sales Campaign

Sales Campaign

We have a sales campaign on our promoted courses and products. You can purchase 1 products at a discounted price up to 15% discount.