python
63 lines · 6 steps
Building a CRUD router with FastAPI
A self-contained projects router uses router-level auth, Pydantic schemas, and injected services to power four ownership-aware endpoints.
Explained by
highlit
1from fastapi import APIRouter, Depends, HTTPException, status
2from pydantic import BaseModel
3
4from .dependencies import get_current_user, get_project_service
5from .services import ProjectService
6from .models import User
7
8router = APIRouter(
9 prefix="/projects",
10 tags=["projects"],
11 dependencies=[Depends(get_current_user)],
12 responses={404: {"description": "Project not found"}},
13)
14
15
16class ProjectIn(BaseModel):
17 name: str
18 description: str | None = None
19
20
21class ProjectOut(ProjectIn):
22 id: int
23 owner_id: int
24
25
26@router.get("", response_model=list[ProjectOut])
27async def list_projects(
28 user: User = Depends(get_current_user),
29 service: ProjectService = Depends(get_project_service),
30):
31 return await service.list_for_owner(user.id)
32
33
34@router.post("", response_model=ProjectOut, status_code=status.HTTP_201_CREATED)
35async def create_project(
36 payload: ProjectIn,
37 user: User = Depends(get_current_user),
38 service: ProjectService = Depends(get_project_service),
39):
40 return await service.create(owner_id=user.id, **payload.model_dump())
41
42
43@router.get("/{project_id}", response_model=ProjectOut)
44async def get_project(
45 project_id: int,
46 user: User = Depends(get_current_user),
47 service: ProjectService = Depends(get_project_service),
48):
49 project = await service.get(project_id)
50 if project is None or project.owner_id != user.id:
51 raise HTTPException(status.HTTP_404_NOT_FOUND, "Project not found")
52 return project
53
54
55@router.delete("/{project_id}", status_code=status.HTTP_204_NO_CONTENT)
56async def delete_project(
57 project_id: int,
58 user: User = Depends(get_current_user),
59 service: ProjectService = Depends(get_project_service),
60):
61 deleted = await service.delete(project_id, owner_id=user.id)
62 if not deleted:
63 raise HTTPException(status.HTTP_404_NOT_FOUND, "Project not found")
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Router-level dependencies apply a cross-cutting concern like authentication to every route without repeating it.
- 2Injecting a service keeps handlers thin, delegating persistence and business logic away from the HTTP layer.
- 3Enforcing ownership checks per resource turns a generic lookup into secure, tenant-scoped access.
Related explainers
typescript
import { registerLocaleData } from '@angular/common'; import localeFr from '@angular/common/locales/fr'; import localeFrExtra from '@angular/common/locales/extra/fr'; import localeDe from '@angular/common/locales/de';
Locale-aware bootstrapping in Angular
i18n
localization
dependency-injection
Intermediate
8 steps
python
from fastapi import FastAPI, WebSocket, WebSocketDisconnect app = FastAPI()
Building a WebSocket chat with FastAPI
websockets
broadcast
connection-management
Intermediate
9 steps
typescript
import { Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import * as Joi from 'joi';
Validating env config at boot in NestJS
configuration
schema-validation
environment-variables
Intermediate
8 steps
java
@Component @Converter public class EncryptedStringConverter implements AttributeConverter<String, String> {
Transparent column encryption in Spring & JPA
encryption
aes-gcm
jpa-converter
Advanced
10 steps
python
import time import uuid from django.utils.deprecation import MiddlewareMixin
Attaching per-request context in Django
middleware
request lifecycle
multi-tenancy
Intermediate
7 steps
typescript
import { Inject, Injectable, Logger } from '@nestjs/common'; import { CACHE_MANAGER } from '@nestjs/cache-manager'; import { Cache } from 'cache-manager'; import { InjectRepository } from '@nestjs/typeorm';
A cache-aside country lookup in NestJS
cache-aside
dependency-injection
batch-lookup
Intermediate
8 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/building-a-crud-router-with-fastapi-explained-python-ea68/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.