Getting started with REST API Web Services in Rust using Axum, PostgreSQL, Redis, and JWT
DRAFT
GitHub: https://github.com/sheroz/axum-rest-api-sample
Rust is gaining increasing popularity each year. Today, Rust is a powerful and proven technology that offers elegant solutions for developing efficient and secure software without the overhead of a garbage collector. I believe the upcoming 2024 edition will further accelerate Rust's adoption and reinforce its position among other technologies.
Rust is a great tool in the right context. One area where using Rust can be beneficial is in developing mission-critical web backend components or systems that require high levels of efficiency and safety, where consistent performance and resource consumption, as well as robust security, are essential.
However, getting started with Rust development can take some effort for newcomers (even for experienced developers). Learning and entering the ever-growing and diverse Rust ecosystem can also take extra time.
I hope this starter project will help you move forward faster in your exciting Rust journey.
Table of contents
- Architecture
- Project structure
- Used technologies
- Routing and request handling
- API versioning
- API error handling in structured format
- Cross-Origin Resource Sharing (CORS)
- REST API endpoints
- REST API request samples
- Configuration and settings
- Authentication & authorization using JSON Web Tokens (JWT)
- Using PostgreSQL database with SQLx
- Using Redis in-memory storage
- Logging
- Graceful shutdown
- End-to-end API tests
- Running the service (debug build):
- Using Docker for building and running services
- GitHub CI configuration
Architecture
The project follows the principles of Clean Architecture, which emphasizes separation of concerns and independence of frameworks, databases, and other services. This design approach ensures that the core business logic is independent, easy to test and maintain, and external services can be replaced if necessary.
Project structure
The project is organized into the following main components:
src
: Contains the main application code.api
: API endpoints and request handling (aka presentation).application
: Business logic and application services.domain
: Core business entities and domain logic.infrastructure
: Database and external service integrations.
tests
: Contains end-to-end tests for the API.
Used technologies
- Axum is a web application framework that focuses on ergonomics and modularity. It is built on top of hyper and tokio runtime, and provides a powerful and flexible way to build web services in Rust.
- JSON Web Tokens (JWT) is used for authentication and authorization. It allows secure transmission of information between parties as a JSON object. The project uses jsonwebtoken crate for login, logout, refresh, and revoking operations.
- PostgreSQL is used as the primary database for storing application data. The project uses SQLx for all database related operations, including database migrations.
- Redis is used for caching and managing tokens. It provides fast, in-memory data storage and retrieval.
API error handling in structured format
REST API endpoints respond with structured API errors, providing consistent and meaningful error handling. The error handling is implemented using serde and thiserror crates.
API error response format
Each error response follows a structured format to ensure consistency and clarity. The API error response structure includes the following fields:
status
: The HTTP status code of the error (e.g., 404, 422).errors
: An array of error entries, each containing detailed information about the error.
Each error entry can include the following fields:
code
: [optional] A unique error code representing the specific error (e.g.,user_not_found
).kind
: [optional] The type of error (e.g.,resource_not_found
,validation_error
).message
: [required] A brief message describing the error.description
: [optional] A detailed description of the error.detail
: [optional] Additional details about the error (e.g., relevant IDs or parameters).reason
: [optional] The reason for the error.instance
: [optional] The URI of the request that caused the error.trace_id
: [optional] A server-generated identifier for tracking the error in logs.timestamp
: [required] The time when the error occurred.help
: [optional] Guidance for resolving the error or further information.doc_url
: [optional] A URL to the documentation for more information about the error.
API error response samples
The optional, server-generated trace_id
field can be useful for easily matching and tracking
errors between consumers and the service (logs).
Note that the returned error structure may contain multiple error entries in a single error message, which can be useful for simpler and clearer user guidance in the UI.
For the list of error codes please refer to API documentation.
Cross-Origin Resource Sharing (CORS)
Cross-Origin Resource Sharing (CORS) is handled to allow or restrict resources on a web server depending on where the HTTP request was initiated.
REST API endpoints
- List of available API endpoints: docs/api-docs.md
- API request samples in the format RFC 2616: tests/endpoints.http
Public Endpoints
- Health:
GET /v1/health
- Version:
GET /v1/version
Authentication
- Login:
POST /v1/auth/login
- Refresh Tokens:
POST /v1/auth/refresh
- Logout:
POST /v1/auth/logout
- Revoke Tokens Issued to the User:
POST /v1/auth/revoke-user
- Revoke All Issued Tokens:
POST /v1/auth/revoke-all
- Cleanup Revoked Tokens:
POST /v1/auth/cleanup
Users
- List Users:
GET /v1/users
- Get User by ID:
GET /v1/users/{user_id}
- Add a New User:
POST /v1/users
- Update User:
PUT /v1/users/{user_id}
- Delete User:
DELETE /v1/users/{user_id}
Accounts
- List Accounts:
GET /v1/accounts
- Get Account by ID:
GET /v1/accounts/{account_id}
- Add a New Account:
POST /v1/accounts
- Update Account:
PUT /v1/accounts/{account_id}
Transactions
- Transfer Money:
POST /v1/transactions/transfer
- Get Transaction by ID:
GET /v1/transactions/{transaction_id}
REST API request samples
- Using REST Client for VS Code. Supports RFC 2616 used in request samples: tests/endpoints.http
- Using curl
Health check
Login
List of users
Configuration and settings
Rust ecosystem has some well-known and rich-featured crates, such as clap and config-rs, that can be used to read and easily parse parameters from the CLI or configuration files. Each has its own strengths and use cases.
This project uses environment variables to configure the service. Configuration parameters from the ENV file are loaded into the environment using the dotenvy crate.
The configuration file has the following settings:
Service configuration
SERVICE_HOST
: The hostname or IP address where the service is running.SERVICE_PORT
: The port number on which the service listens.
Redis configuration
REDIS_HOST
: The hostname or IP address of the Redis server.REDIS_PORT
: The port number on which the Redis server listens.
PostgreSQL configuration
POSTGRES_USER
: The username for connecting to the PostgreSQL database.POSTGRES_PASSWORD
: The password for connecting to the PostgreSQL database.POSTGRES_HOST
: The hostname or IP address of the PostgreSQL server.POSTGRES_PORT
: The port number on which the PostgreSQL server listens.POSTGRES_DB
: The name of the PostgreSQL database.POSTGRES_CONNECTION_POOL
: The number of connections in the PostgreSQL connection pool.
JWT (JSON Web Token) configuration
JWT_SECRET
: The secret key used for signing JWTs.JWT_EXPIRE_ACCESS_TOKEN_SECONDS
: The expiration time for access tokens in seconds.JWT_EXPIRE_REFRESH_TOKEN_SECONDS
: The expiration time for refresh tokens in seconds.JWT_VALIDATION_LEEWAY_SECONDS
: The leeway time in seconds for validating JWTs.JWT_ENABLE_REVOKED_TOKENS
: Enable or disable the use of revoked tokens. Set totrue
to allow revoked tokens,false
to disallow.
Authentication & authorization using JSON Web Tokens (JWT)
The project contains examples of the following operations:
- Login, logout, refresh, and revoking operations
- Role based authorization
- Generating and validating access and refresh tokens
- Setting tokens expiry time (based on configuration)
- Using refresh tokens rotation technique
- Revoking issued tokens by using Redis (based on configuration)
- Revoke all tokens issued until the current time
- Revoke tokens belonging to the user issued until the current time
- Cleanup of revoked tokens
Using PostgreSQL database with SQLx
The project contains examples of the following database operations:
- Database migrations
- Async connection pooling
- Async CRUD operations and transactions
Using Redis in-memory storage
The project contains examples of the following Redis operations:
- Async Redis operations
Logging
The tracing crate is used for logging.
End-to-end API tests
REST API tests are located at: tests
Using database isolation for tests
Database isolation ensures each test runs by setting up and tearing down the database state before and after each test.
Running tests sequentially
To ensure tests run sequentially and avoid conflicts, the serial_test crate is used by annotating test
functions with #[serial]
.
Running tests
Running the service
Running the service in debug mode
Running the service in test configuration
Running the service at a specific log level
Setting the RUST_LOG
- logging level on the launch:
Using Docker
The project contains examples of using Docker:
- PostgreSQL and Redis services
- Building the application using the official Rust image
- Running the full stack: API + PostgreSQL + Redis
Running the Docker based full stack build
GitHub CI configuration
The project has continuous integration (CI) using GitHub Actions, which automates the following workflows:
- Running
cargo deny
to check for security vulnerabilities and licenses - Running
cargo fmt
to check for the Rust code format according to style guidelines - Running
cargo clippy
to catch common mistakes and improving the Rust code - Running tests
- Building the application
The GitHub CI file is located at: ci.yml
The source code is available on GitHub: https://github.com/sheroz/axum-rest-api-sample
2023 September (1)
2023 August (1)
2019 May (1)
2016 March (2)
2016 February (1)
2014 December (1)
2013 May (1)
2013 March (1)
2013 February (1)
2012 December (2)
2012 October (1)
2011 February (2)
2010 October (2)
2010 July (1)
2010 May (1)
2010 April (1)