A blog application built with FastAPI, featuring both:
- a REST API (
/api/...) for programmatic access - a server-rendered website using Jinja2 templates
This project is a application built not only as a functional website, but also as a reusable blueprint for any system that requires similar core behavior — such as user accounts, login/logout flows, post creation, chronological content organization, and full CRUD operations.
While the business logic could easily be adapted to other domains (news platforms, forums, social feeds, product reviews, etc.), the underlying architecture is designed to be stable, clean, and scalable.
Authentication and authorization are currently under development to fully secure routes and user actions.
This project take the following principles as its building concerns:
- async FastAPI architecture
- modular routers design
- clean separation of concerns
- SQLAlchemy ORM relationships
- template rendering + static file serving
- proper exception handling for both API + frontend
- production-style project layout
-
Homepage displaying all posts (latest first)
-
Single post page (
/posts/{id}) -
User posts page (
/users/{id}/posts) -
Bootstrap layout with responsive design
-
Bootstrap modals for:
- Creating new posts
- Editing posts
- Deleting posts
-
Light/Dark/Auto theme toggle
-
Profile picture support (default image if missing)
-
Custom error page rendering for web routes
- Create user
- Get a user by ID
- List all users
- Get all posts of a user
- Update user partially (PATCH)
- Delete user
- List all posts (ordered by most recent)
- Get post by ID
- Create post
- Full update (PUT)
- Partial update (PATCH)
- Delete post
All endpoints use Pydantic schemas for validation and response formatting.
-
Async SQLAlchemy ORM models (
User,Post) -
SQLite database (
blog.db) -
Automatic table creation at app startup via FastAPI lifespan
-
Proper relationships:
- One User → Many Posts
- Each Post belongs to a User
This project uses custom exception handlers to provide different behavior for:
- API requests (
/api/...) → returns JSON responses (default FastAPI style) - Website requests → renders
error.htmltemplate with friendly UI
Handled errors include:
- Standard HTTP errors (404, 400, etc.)
- Validation errors (422)
- Python 3.13+
- FastAPI
- SQLAlchemy (Async ORM)
- Pydantic
- Uvicorn
- Jinja2 Templates
- Bootstrap 5
- Vanilla JavaScript (Fetch API + Modules)
- SQLite
- Custom CSS
- Icons + manifest (PWA-ready)
- Default profile picture
- Media folder for user profile images
.
├── main.py # FastAPI app entrypoint + webpage routes
├── database.py # Database engine + async session dependency
├── models.py # SQLAlchemy ORM models
├── schemas.py # Pydantic schemas for validation/serialization
├── routers/
│ ├── posts.py # Posts API endpoints
│ ├── users.py # Users API endpoints
│ └── __init__.py
├── templates/ # Jinja2 HTML templates
│ ├── layout.html
│ ├── home.html
│ ├── post.html
│ ├── user_posts.html
│ └── error.html
├── static/ # Static assets (css, js, icons)
│ ├── css/
│ ├── js/
│ ├── icons/
│ └── profile_pics/
├── media/
│ └── profile_pics/ # Uploaded profile images (future use)
├── blog.db # SQLite database
├── requirements.txt # Dependencies list
├── pyproject.toml # Project metadata
└── README.mdgit clone https://github.com/your-username/your-repo-name.git
cd your-repo-namepython -m venv venv
venv\Scripts\activatepython3 -m venv venv
source venv/bin/activatepip install -r requirements.txtStart the FastAPI server using Uvicorn:
uvicorn main:app --reloadNow open:
- Web App: http://127.0.0.1:8000/
- Swagger Docs: http://127.0.0.1:8000/docs
- ReDoc Docs: http://127.0.0.1:8000/redoc
| Route | Description |
|---|---|
/ |
Home page (all posts) |
/posts/{post_id} |
View a single post |
/users/{user_id}/posts |
View posts from a specific user |
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/users |
Create user |
| GET | /api/users |
List users |
| GET | /api/users/{user_id} |
Get user |
| GET | /api/users/{user_id}/posts |
Get user's posts |
| PATCH | /api/users/{user_id} |
Update user |
| DELETE | /api/users/{user_id} |
Delete user |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/posts |
List posts |
| POST | /api/posts |
Create post |
| GET | /api/posts/{post_id} |
Get post |
| PUT | /api/posts/{post_id} |
Full update |
| PATCH | /api/posts/{post_id} |
Partial update |
| DELETE | /api/posts/{post_id} |
Delete post |
curl -X POST http://127.0.0.1:8000/api/users \
-H "Content-Type: application/json" \
-d '{
"username": "adan",
"email": "adan@email.com"
}'curl -X POST http://127.0.0.1:8000/api/posts \
-H "Content-Type: application/json" \
-d '{
"title": "My First Post",
"content": "This is my first blog post!",
"user_id": 1
}'Users support an optional image_file field.
- If the user has an uploaded profile image, the project serves it from:
/media/profile_pics/<filename>
- If not, it automatically falls back to:
/static/profile_pics/default.jpg
This logic is implemented in the User.image_path property inside models.py.
This project is currently in early development and still lacks authentication.
Planned future improvements:
- Authentication system (JWT or session cookies)
- Register/Login UI integration
- Restrict post creation/edit/delete to authenticated users
- File upload support for profile pictures
- Pagination on posts list
- Search and filtering
- Deployment setup (Docker + Render/Fly.io)
In the templates, post actions are temporarily hardcoded to user ID 3.
Example:
- New post form automatically sets:
postData.user_id = 3;And post edit/delete buttons are only shown if:
{% if post.user_id == 3 %}This will be replaced once authentication is implemented.