A production-style REST API for managing personal notes. Built with Rust, Axum, and PostgreSQL. Features JWT-based authentication, pagination, OpenAPI documentation with Swagger UI, and Docker support.
Build a secure, performant, and well-documented REST API that demonstrates production-grade Rust backend development practices. The project serves as both a functional notes management service and a reference implementation for modern Rust web development.
Many Rust backend tutorials and starter projects lack real-world production features such as:
- Structured error handling with proper HTTP status codes
- JWT-based authentication with password hashing
- Request validation and sanitization
- Pagination for list endpoints
- Auto-generated OpenAPI documentation
- Containerized development and deployment
- Clean separation of concerns (controllers, services, repositories)
This gap makes it difficult for developers to learn production patterns in Rust or quickly bootstrap a new API project.
The Rust Notes API provides a complete, well-architected backend with all the essential production features out of the box:
- Clean monolith architecture with clear separation of concerns (routes, controllers, services, middleware, DTOs)
- JWT authentication with Argon2 password hashing for secure credential storage
- CRUD operations for personal notes scoped to authenticated users
- Pagination support with configurable page size
- Global error handling that produces consistent JSON error responses
- Auto-generated OpenAPI 3.1 documentation with Swagger UI
- Structured logging with configurable verbosity
- Docker Compose setup for one-command local development
- Multi-stage Docker build for production deployment
- User registration and login with email and password
- JWT-based authentication (HS256)
- Argon2 password hashing
- Create, read, update, and delete personal notes
- Paginated note listing (sorted by creation date descending)
- Request validation on all inputs
- Consistent JSON error responses
- Health check endpoint
- Swagger UI at
/swagger-ui - OpenAPI JSON spec at
/api/docs/openapi.json - Structured logging with tracing
- PostgreSQL database with migration scripts
- Docker Compose for local development
- Multi-stage Dockerfile for production builds
| Category | Technology | Purpose |
|---|---|---|
| Language | Rust 2021 | Systems programming language |
| Web Framework | Axum 0.7 | Async HTTP framework |
| Runtime | Tokio 1 (full features) | Async runtime |
| Database | PostgreSQL 16 | Relational database |
| ORM | SeaORM 1 | Async database ORM |
| Auth | JSON Web Token (jsonwebtoken 9) | Token-based authentication |
| Hashing | Argon2 0.5 | Password hashing |
| Serialization | Serde 1 | JSON serialization/deserialization |
| Validation | Validator 0.18 | Request payload validation |
| Error Handling | ThisError 2 | Ergonomic error type definitions |
| Logging | Tracing 0.1 + tracing-subscriber | Structured logging |
| OpenAPI | Utoipa 5 + utoipa-swagger-ui 8 | API documentation |
| Middleware | Tower 0.4 + tower-http 0.5 | CORS, tracing middleware |
| Deployment | Docker + Docker Compose | Containerization |
The following diagram shows how an HTTP request flows through the application:
sequenceDiagram
participant Client as Client (Browser/cURL)
participant Router as Axum Router
participant Middleware as Auth Middleware
participant Controller as Controller
participant Service as Service
participant DB as PostgreSQL
Client->>Router: HTTP Request
Router->>Middleware: Pass through auth middleware (if route is protected)
Middleware->>Middleware: Extract and validate JWT from Authorization header
Middleware->>Router: Attach AuthUser to request extensions
Router->>Controller: Route to handler with extracted parameters
Controller->>Service: Call service with domain data
Service->>DB: Query/Modify database via SeaORM
DB-->>Service: Return result
Service-->>Controller: Return domain result
Controller-->>Router: Convert to HTTP response (JSON)
Router-->>Client: HTTP Response
rust-notes-api/
|
|-- src/
| |-- main.rs # Application entrypoint, server startup
| |-- config.rs # Environment variable configuration
| |-- database.rs # Database connection with retry logic
| |-- errors.rs # Global error handling (AppError)
| |-- migration.rs # SQL table creation migrations
| |-- docs.rs # OpenAPI documentation definition
| |-- app_state.rs # Shared application state
| |
| |-- entities/ # SeaORM entity models
| | |-- user.rs # User model
| | |-- note.rs # Note model
| | |-- mod.rs
| |
| |-- dto/ # Data Transfer Objects
| | |-- auth.rs # Register, Login, AuthResponse DTOs
| | |-- note.rs # Create, Update, NoteResponse, Pagination DTOs
| | |-- mod.rs
| |
| |-- controllers/ # HTTP request handlers
| | |-- auth_controller.rs # Auth endpoint handlers
| | |-- note_controller.rs # Note CRUD endpoint handlers
| | |-- mod.rs
| |
| |-- services/ # Business logic layer
| | |-- auth_service.rs # Registration and login logic
| | |-- note_service.rs # Note CRUD logic with pagination
| | |-- mod.rs
| |
| |-- middleware/ # Request middleware
| | |-- auth.rs # JWT authentication middleware
| | |-- mod.rs
| |
| |-- routes/ # Route group definitions
| | |-- auth_routes.rs # Auth route grouping
| | |-- note_routes.rs # Note route grouping
| | |-- mod.rs
| |
| |-- utils/ # Helper utilities
| |-- auth.rs # Password hashing, JWT creation/validation
| |-- mod.rs
|
|-- Cargo.toml # Rust dependency manifest
|-- Cargo.lock # Locked dependency versions
|-- Dockerfile # Multi-stage Docker build
|-- docker-compose.yml # Docker Compose setup (PostgreSQL + API)
|-- .env # Local environment variables
|-- .env.example # Example environment file
|-- .dockerignore # Docker build ignore rules
|-- .gitignore # Git ignore rules
erDiagram
users {
int id PK "Auto-increment"
varchar email "Unique"
text password_hash
timestamp created_at
timestamp updated_at
}
notes {
int id PK "Auto-increment"
int user_id FK "References users.id"
varchar title
text content
timestamp created_at
timestamp updated_at
}
users ||--o{ notes : "has many"
flowchart TD
A["Client: POST /api/notes"] --> B["Auth Middleware: validate JWT"]
B --> C{"JWT Valid?"}
C -->|"No"| D["401 Unauthorized"]
C -->|"Yes"| E["Extract user_id from token"]
E --> F["Controller: validate request body"]
F --> G{"Valid?"}
G -->|"No"| H["400 Bad Request"]
G -->|"Yes"| I["Service: create note in database"]
I --> J["Insert into notes table\n(user_id, title, content)"]
J --> K["Return created note as JSON"]
| Method | Path | Description | Auth Required |
|---|---|---|---|
| GET | /health |
Health check | No |
| Method | Path | Description | Auth Required |
|---|---|---|---|
| POST | /api/auth/register |
Register new user | No |
| POST | /api/auth/login |
Login user | No |
| GET | /api/me |
Get current user | Yes |
| Method | Path | Description | Auth Required |
|---|---|---|---|
| GET | /api/notes |
List notes (paginated) | Yes |
| POST | /api/notes |
Create a note | Yes |
| GET | /api/notes/:id |
Get a note by ID | Yes |
| PUT | /api/notes/:id |
Update a note | Yes |
| DELETE | /api/notes/:id |
Delete a note | Yes |
| Method | Path | Description | Auth Required |
|---|---|---|---|
| GET | /swagger-ui |
Swagger UI | No |
| GET | /api/docs/openapi.json |
OpenAPI JSON spec | No |
- Rust 2021 edition (1.75 or later)
- Docker and Docker Compose (for PostgreSQL and containerized deployment)
- Cargo (included with Rust)
git clone <repository-url>
cd rust-notes-apicp .env.example .env
# Edit .env if needed (defaults work for local development)docker compose up -d postgresThis starts a PostgreSQL 16 container on port 5432 with:
- Username:
postgres - Password:
postgres - Database:
rust_notes_api
cargo runThe server starts at http://0.0.0.0:3000. Database migrations run automatically on startup.
Open http://localhost:3000/swagger-ui in your browser to explore and test the API interactively.
docker compose up --buildThis builds the API container and starts both PostgreSQL and the API service. The API is available at http://localhost:3000.
curl -X POST http://localhost:3000/api/auth/register \
-H "Content-Type: application/json" \
-d '{"email": "[email protected]", "password": "securePassword123"}'curl -X POST http://localhost:3000/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email": "[email protected]", "password": "securePassword123"}'Save the returned token value.
curl -X POST http://localhost:3000/api/notes \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <your-token>" \
-d '{"title": "My first note", "content": "Hello, world!"}'curl "http://localhost:3000/api/notes?page=1&limit=10" \
-H "Authorization: Bearer <your-token>"curl http://localhost:3000/api/notes/1 \
-H "Authorization: Bearer <your-token>"curl -X PUT http://localhost:3000/api/notes/1 \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <your-token>" \
-d '{"title": "Updated title", "content": "Updated content"}'curl -X DELETE http://localhost:3000/api/notes/1 \
-H "Authorization: Bearer <your-token>"curl http://localhost:3000/health| Variable | Required | Default | Description |
|---|---|---|---|
DATABASE_URL |
Yes | - | PostgreSQL connection string |
JWT_SECRET |
Yes | - | Secret key for JWT signing |
JWT_EXPIRATION_DAYS |
No | 7 |
JWT token expiration in days |
HOST |
No | 0.0.0.0 |
Server bind address |
PORT |
No | 3000 |
Server port |
RUST_LOG |
No | - | Logging verbosity (e.g., debug) |
| Layer | Responsibility | Examples |
|---|---|---|
| Routes | Define URL paths and HTTP method mappings | /api/notes -> GET -> list |
| Controllers | Extract request data, call services, format responses | Parse JSON body, call service |
| Services | Implement business logic and database queries | Register user, create note |
| Middleware | Process requests before/after handlers | JWT validation, logging |
| DTOs | Define request/response shapes and validation rules | RegisterDto, CreateNoteDto |
| Entities | Map database tables to Rust structs | User, Note models |
All errors flow through the AppError enum which implements Axum's IntoResponse trait:
| Error Variant | HTTP Status | Description |
|---|---|---|
ValidationError |
400 | Invalid input data |
Unauthorized |
401 | Missing or invalid JWT |
Forbidden |
403 | Insufficient permissions |
NotFound |
404 | Resource does not exist |
Conflict |
409 | Duplicate resource (e.g., email) |
InternalServerError |
500 | Unexpected server failure |
Response format:
{
"error": {
"message": "Validation error: email is required",
"status": 400
}
}- Refresh token rotation for enhanced auth security
- Email verification flow on registration
- Rate limiting to prevent brute force attacks
- Role-based access control (RBAC)
- Soft delete for notes with restore capability
- Note sharing between users
- Search and filtering by title/content
- Sort order customization (by title, updated_at, etc.)
- Automated integration tests
- CI/CD pipeline with GitHub Actions
- Health check endpoint with database connectivity check
- Prometheus metrics endpoint
- Database connection pooling tuning
- Request ID tracing across middleware and logs
MIT