Skip to main content

Capstone Project - Secure Blog Posts API with Role-Based Access Control (RBAC)

Problem Statement In this project, you will build a secure, production-ready Blog Posts REST API that manages users and blog posts with a one-to-many relationship using SQLAlchemy ORM. You will implement role-based authorization with two user roles:
  • Author: Can register, authenticate, view all posts, and create/update/delete their own posts.
  • Admin: Has full access to manage any user account and delete or update any blog post on the platform.
The project will follow a clean, modular structure utilizing repository layers, service layers, global exception handling, modern Pydantic schema inheritance, and FastAPI’s Annotated dependency injection.

Learning Objectives

After completing this project, you will be able to:
  • Model one-to-many relationships in SQLAlchemy and map foreign keys.
  • Structure request validation and response models using Pydantic inheritance.
  • Restrict endpoint operations using JWT-based OAuth2 authentication and scopes/roles.
  • Leverage modern Annotated syntax to perform field validation and dependency injection.
  • Design database access abstraction using the Repository Pattern.
  • Separate business logic cleanly into the Service Layer.
  • Handle API errors globally and return uniform error structures.

Final Project Architecture


Step 1: Create the Project Directory

Objective Create the project directory and initialize the project environment using uv. Instructions
  • Create a new project directory named blog-rbac-api.
  • Navigate to the project directory.
  • Initialize the project structure using uv init.
Task Create the project directory and initialize it using uv.
Code
Run & Verify Verify that:
  • The blog-rbac-api folder is created.
  • A pyproject.toml file is created.

Step 2: Initialize Git and Create Gitignore

Objective Set up version control tracking and declare file paths to ignore. Instructions
  • Initialize a new git repository in the workspace root.
  • Create a .gitignore file mapping path patterns that should not be tracked by Git.
Task Initialize the git repository and define .gitignore.
CodeInitialize Git:
Create .gitignore:
Git ActionsCommit setup:
Run & Verify Verify that git status reports .gitignore is successfully committed.

Step 3: Install Required Dependencies

Objective Install all required third-party libraries for database connection, token authentication, and the web server. Instructions
  • Run uv add to install project dependencies.
Task Install the necessary dependencies.
Code
Git Actions
Run & Verify Verify that pyproject.toml shows the newly installed dependencies under the dependency section.

Step 4: Create the Modular Package Folder Structure

Objective Lay out folders and files to enforce separation of concerns across models, schemas, repositories, services, and routers. Instructions
  • Create the application package directory named app.
  • Create folders for core, models, schemas, repositories, services, and routers under app.
  • Place empty __init__.py files inside each folder.
Task Create the project package directories.
CodeFolder Layout
Git ActionsCommit the folder structure:
Run & Verify Verify that all directory structures are properly set up inside your workspace.

Step 5: Configure the Database Connection

Objective Configure database connection parameters and baseline database session manager settings. Instructions
  • Create app/database.py at the root of the app package.
  • Initialize database engine settings, the session factory, and a base model class Base.
Task Implement database connection settings in app/database.py.
CodeFile: app/database.py
Git ActionsCommit the changes:
Run & Verify Verify that the database configuration script compiles without module-level syntax or import issues.

Step 6: Create the Database Session Dependency

Objective Expose database session dependencies at the global package level, at the same level as the database configuration. Instructions
  • Create app/dependencies.py.
  • Define the get_db generator function to handle database sessions.
Task Implement the get_db database session generator dependency.
CodeFile: app/dependencies.py
Git ActionsCommit the changes:
Run & Verify Verify that the dependency loader file compiles correctly.

Step 7: Define Global Exceptions

Objective Design exception models to categorize API errors cleanly. Instructions
  • Create custom exceptions inside app/core/exceptions.py.
  • Inherit exceptions from a base AppException class.
Task Implement custom exceptions in app/core/exceptions.py.
CodeFile: app/core/exceptions.py
Git ActionsCommit the changes:
Run & Verify Verify that the module has no syntax errors.

Step 8: Define Exception Handlers

Objective Convert custom application errors into structured HTTP responses. Instructions
  • Create app/core/exception_handlers.py.
  • Write the exception handler helper to return JSONResponses.
Task Implement custom exceptions handler mapping.
CodeFile: app/core/exception_handlers.py
Git ActionsCommit the changes:
Run & Verify Verify that exception handlers import matching exception classes successfully.

Step 9: Design the User Database Model

Objective Create the User database model with a role attribute to handle Admin vs. Author permissions. Instructions
  • Create app/models/user.py.
  • Define fields mapping the users table.
  • Declare relationships to user posts.
Task Define the User database model. User Database Schema Details:
  • id: Integer, Primary Key, Indexed
  • email: String(100), Unique, Indexed, Not Null
  • hashed_password: String(200), Not Null
  • name: String(100), Not Null
  • role: String(20), Not Null (Defaults to "author")
  • Relationship: Has a one-to-many relationship with Post mapped via posts back-populating author (using cascade="all, delete-orphan")
CodeFile: app/models/user.py
Git ActionsCommit the changes:
Run & Verify Verify that the models module compiles correctly.

Step 10: Design the Post Database Model

Objective Define the blog Post model referencing the User model via a foreign key relationship. Instructions
  • Create app/models/post.py.
  • Map column attributes for the posts table.
  • Configure ForeignKey relations pointing user identities to author identifiers.
Task Define the Post database model. Post Database Schema Details:
  • id: Integer, Primary Key, Indexed
  • title: String(200), Not Null
  • content: Text, Not Null
  • author_id: Integer, ForeignKey to users.id (with cascade delete rules), Not Null
  • Relationship: Belongs to User back-populating posts
CodeFile: app/models/post.py
Git ActionsCommit the changes:
Run & Verify Verify that the foreign key constraints align with standard schema maps.

Step 11: Create User Validation Schemas

Objective Create User data validation schemas using Pydantic inheritance and modern Annotated validators. Instructions
  • Create app/schemas/user.py.
  • Use inheritance starting from UserBase.
  • Add field validation constraints.
Task Define User validation schemas using Pydantic inheritance. User Pydantic Schemas Details:
  • UserBase:
    • email: Validated EmailStr
    • name: string (length between 2 and 50)
  • UserCreate: Inherits from UserBase and adds password (minimum length of 6) and role (defaults to "author")
  • UserUpdate: Optional fields for name, email, and role
  • UserResponse: Inherits from UserBase and adds id and role (configured with from_attributes = True)
CodeFile: app/schemas/user.py
Git ActionsCommit the changes:
Run & Verify Verify that schema classes run input type checks.

Step 12: Create Post Validation Schemas

Objective Implement data validation schemas for creating, updating, and returning blog posts. Instructions
  • Create app/schemas/post.py.
  • Implement PostBase and its subclasses.
Task Define Post validation schemas using Pydantic inheritance. Post Pydantic Schemas Details:
  • PostBase:
    • title: string (length between 3 and 200)
    • content: string (minimum length of 1)
  • PostCreate: Inherits from PostBase
  • PostUpdate: Optional fields for title and content
  • PostResponse: Inherits from PostBase and adds id, author_id, and author (configured with from_attributes = True)
CodeFile: app/schemas/post.py
Git ActionsCommit the changes:
Run & Verify Verify that the schemas inherit properties correctly.

Step 13: Create Token Validation Schemas

Objective Create Pydantic models for authentication JWT tokens. Instructions
  • Create app/schemas/token.py.
  • Define models to validate login responses.
Task Define Token validation schemas.
CodeFile: app/schemas/token.py
Git ActionsCommit the changes:
Run & Verify Verify that the Token schemas map response keys.

Step 14: Core Security and Password Utilities

Objective Provide helper utilities for secure password storage. Instructions
  • Create app/core/security.py.
  • Define helper actions to hash credentials and verify passwords.
Task Implement password hashing and password verification methods.
CodeFile: app/core/security.py
Git ActionsCommit the changes:
Run & Verify Verify that the password hashing is non-reversible.

Step 15: Create the User Repository

Objective Separate direct database operations on the User records using a Repository class. Instructions
  • Create app/repositories/user_repository.py.
  • Implement routines to retrieve, create, and update User rows.
Task Implement UserRepository.
CodeFile: app/repositories/user_repository.py
Git ActionsCommit the changes:
Run & Verify Verify that database queries match standard SQLAlchemy formats.

Step 16: Create the Post Repository

Objective Separate database operations on the Post records using a Repository class. Instructions
  • Create app/repositories/post_repository.py.
  • Implement database queries to create, read, update, and delete Post entries.
Task Implement PostRepository.
CodeFile: app/repositories/post_repository.py
Git ActionsCommit the changes:
Run & Verify Verify that CRUD database queries compile without errors.

Step 17: Create the User Service

Objective Coordinate User validation and profile updates within the Service Layer. Instructions
  • Create app/services/user_service.py.
  • Write user profile updates and permission checks.
Task Implement UserService.
CodeFile: app/services/user_service.py
Git ActionsCommit the changes:
Run & Verify Verify that users without the Admin role cannot update roles.

Step 18: Create the Post Service

Objective Coordinate Post mutations and authorization limit audits within the Service Layer. Instructions
  • Create app/services/post_service.py.
  • Write logic to restrict post updates and deletions to the post owner or users with the Admin role.
Task Implement PostService.
CodeFile: app/services/post_service.py
Git ActionsCommit the changes:
Run & Verify Verify that non-owners and non-admins receive a 403 Forbidden error when trying to delete a post.

Step 19: Implement Authentication Dependencies in Dependencies.py

Objective Expose current active user context validation helper in the global dependencies file. Instructions
  • Open app/dependencies.py.
  • Append the authentication dependencies (get_current_user and annotations) to the same level as get_db.
Task Implement OAuth2 JWT authentication dependency in app/dependencies.py.
CodeFile: app/dependencies.py
Git ActionsCommit dependency updates:
Run & Verify Verify that app/dependencies.py compiles cleanly containing both database session and OAuth2 authentication handlers.

Step 20: Create API Routers

Objective Expose operations through API Routers using Annotated injection syntax. Instructions
  • Create app/routers/auth.py, app/routers/user.py, and app/routers/post.py.
  • Import dependencies from app.dependencies to enforce authorization.
Task Implement API routers.
CodeAuth Router (app/routers/auth.py)
User Router (app/routers/user.py)
Post Router (app/routers/post.py)
Git ActionsCommit routers:
Run & Verify Verify that routers compile cleanly.

Step 21: Setup Application Entrypoint (main.py)

Objective Integrate your routes, create database tables, register global exception handlers, and start your server. Instructions
  • Create app/main.py.
  • Include the declarative base model definitions to create tables on startup.
  • Register AppException handler using your custom function handler.
  • Register the Auth, User, and Post routers.
Task Assemble the final FastAPI configuration in main.py.
Code
Git ActionsCommit the final main configuration:
Run & Verify Verify your completed project works as expected:
  • Start your server:
  • Navigate to http://127.0.0.1:8000/docs to test endpoints via Swagger UI.
  • Test endpoint access:
    • Register an admin user and an author user.
    • Test that authors can edit their own posts but get 403 Forbidden trying to edit others.
    • Test that admins can modify or delete any post and view/manage the user accounts list.