This is a flexible Rust web server using Actix-web, Diesel ORM, and PostgreSQL.
Before you begin, ensure you have the following installed:
The project is organized into the following structure:
src/
├── controllers/
│ ├── mod.rs
│ └── auth.rs
├── repositories/
│ ├── mod.rs
│ └── user_repository.rs
├── services/
│ ├── mod.rs
│ └── auth_service.rs
├── models.rs
├── schema.rs
├── errors.rs
├── dto/
│ ├── mod.rs
│ ├── auth_response.rs
│ ├── claims.rs
│ ├── login_user.rs
│ ├── register_user.rs
│ └── user.rs
└── lib.rs
-
Controllers: Handle HTTP requests and responses. They use services to process business logic.
auth.rs: Manages authentication-related endpoints (register, login, refresh).
-
Repositories: Interact with the database, performing CRUD operations.
user_repository.rs: Handles database operations for user-related data.
-
Services: Contain the core business logic of the application.
auth_service.rs: Implements authentication logic, including user registration, login, and token refresh.
-
Models: Define the data structures that represent database tables.
models.rs: Contains structs for User and RefreshToken.
-
Schema: Auto-generated by Diesel, represents the database schema.
schema.rs: Defines the structure of database tables.
-
Errors: Custom error handling for the application.
errors.rs: Defines the ApiError type for consistent error responses.
-
DTOs (Data Transfer Objects): Structures for data exchange between layers.
auth_response.rs: Defines the structure for authentication responses.claims.rs: Contains JWT claims structure.login_user.rsandregister_user.rs: Structures for login and registration requests.user.rs: DTO for user data.
-
lib.rs: The library's root file, re-exporting modules and setting up shared components.
This structure promotes separation of concerns, making the application more modular and easier to maintain. Each component has a specific responsibility:
- Controllers handle the web interface.
- Repositories manage data persistence.
- Services implement business logic.
- Models and DTOs define data structures.
- The errors module provides consistent error handling across the application.
To add a new repository:
- Create a new file in the
src/repositories/directory (e.g.,new_repository.rs). - Define your repository struct and implement methods for database operations.
- Add a
pub mod new_repository;line insrc/repositories/mod.rs.
Example:
// src/repositories/new_repository.rs
use diesel::prelude::*;
use crate::{DbPool, NewModel, schema::new_table};
pub struct NewRepository;
impl NewRepository {
pub fn create(pool: &DbPool, new_item: &NewModel) -> Result<NewModel, diesel::result::Error> {
let mut conn = pool.get().expect("Couldn't get db connection from pool");
diesel::insert_into(new_table::table)
.values(new_item)
.get_result(&mut conn)
}
// Add more methods as needed
}To add a new controller:
- Create a new file in the
src/controllers/directory (e.g.,new_controller.rs). - Define your controller functions that handle HTTP requests.
- Add a
pub mod new_controller;line insrc/controllers/mod.rs.
Example:
// src/controllers/new_controller.rs
use actix_web::{web, HttpResponse, Responder};
use crate::{DbPool, NewModel};
use crate::services::new_service::NewService;
pub async fn create_new_item(
pool: web::Data<DbPool>,
item_info: web::Json<NewModel>,
) -> impl Responder {
match NewService::create(&pool, &item_info.into_inner()) {
Ok(_) => HttpResponse::Ok().json("Item successfully created"),
Err(_) => HttpResponse::InternalServerError().json("Failed to create item"),
}
}
// Add more controller functions as neededAfter adding new repositories, services, and controllers, update src/main.rs to include the new routes:
use actix_web::{web, App, HttpServer};
use your_project_name::controllers::auth;
use your_project_name::controllers::new_controller;
#[actix_web::main]
async fn main() -> std::io::Result<()> {
// ... (database setup code)
HttpServer::new(move || {
App::new()
.app_data(web::Data::new(pool.clone()))
.service(
web::scope("/api")
.configure(auth::configure)
.service(
web::scope("/new")
.route("/create", web::post().to(new_controller::create_new_item))
)
)
})
.bind("127.0.0.1:8080")?
.run()
.await
}This structure allows for easy addition of new features and maintains a clean separation of concerns.
To set up the database for this project, follow these steps:
-
Start the PostgreSQL service:
sudo systemctl start postgresql -
Create a new database for the project:
sudo -u postgres psql -c "CREATE DATABASE rust_web_server;" -
Create a new user and grant privileges:
sudo -u postgres psql -c "CREATE USER your_username WITH PASSWORD 'your_password';" sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE rust_web_server TO your_username;"
-
Open the PostgreSQL shell (SQL Shell) from the Start menu.
-
Connect to the default database (usually 'postgres') by pressing Enter for each prompt until you reach the password prompt. Enter your PostgreSQL superuser password.
-
Create a new database for the project:
CREATE DATABASE rust_web_server; -
Create a new user and grant privileges:
CREATE USER your_username WITH PASSWORD 'your_password'; GRANT ALL PRIVILEGES ON DATABASE rust_web_server TO your_username;
-
Create a
.envfile in the project root directory and add your database connection details:DATABASE_URL=postgres://your_username:your_password@localhost/rust_web_server -
Install the Diesel CLI (if not already installed):
cargo install diesel_cli --no-default-features --features postgres -
Run database migrations:
diesel migration run
After completing these steps, your database should be set up and ready for use with the Rust web server.
To run the tests for this project, follow these steps:
-
Ensure that your database is set up and running as described in the Database Setup section.
-
Make sure you are in the project root directory.
-
Run all tests using the following command:
cargo testThis will compile your code and run all the tests, including unit tests and integration tests.
-
To run a specific test, you can use:
cargo test test_nameReplace
test_namewith the name of the test you want to run. -
To see the output of
println!statements in tests, use:cargo test -- --nocapture -
To run tests with logging, set the
RUST_LOGenvironment variable:- On Linux/macOS:
RUST_LOG=debug cargo test - On Windows (PowerShell):
$env:RUST_LOG="debug"; cargo test - On Windows (Command Prompt):
set RUST_LOG=debug && cargo test
- On Linux/macOS:
Remember to keep your tests up to date as you add new features or modify existing functionality.
This section provides instructions for running the application on both Unix and Windows systems.
- Open a terminal and navigate to the project root directory.
- Run the following command to start the server:
cargo run - The server will start and listen on
http://127.0.0.1:8080by default.
- Open a Command Prompt or PowerShell window and navigate to the project root directory.
- Run the following command to start the server:
cargo run - The server will start and listen on
http://127.0.0.1:8080by default.
For both Unix and Windows systems, you can now send requests to the API endpoints using tools like curl, Postman, or any HTTP client.
This project implements several security features to protect user data and prevent unauthorized access:
-
Password Hashing: User passwords are hashed using bcrypt before being stored in the database. This ensures that even if the database is compromised, the actual passwords remain secure.
-
JWT (JSON Web Token) Authentication: The application uses JWT for secure authentication. When a user logs in successfully, they receive a short-lived access token and a longer-lived refresh token.
-
Refresh Token Mechanism: To improve security while maintaining user convenience, the application implements a refresh token system. This allows users to obtain a new access token without re-entering their credentials, as long as their refresh token is valid.
-
HttpOnly Cookies: Refresh tokens are stored in HttpOnly cookies, which helps prevent cross-site scripting (XSS) attacks by making the cookie inaccessible to client-side scripts.
-
CORS (Cross-Origin Resource Sharing) Configuration: The application includes a CORS configuration to control which domains can access the API, helping to prevent unauthorized access from other websites.
-
Environment Variables: Sensitive information such as database credentials and JWT secrets are stored in environment variables, keeping them out of the source code and making the application more secure and configurable.
-
Custom Error Handling: The application uses custom error types and handling to ensure that detailed error information is not leaked to clients, which could potentially expose vulnerabilities.
-
Database Connection Pooling: The use of connection pooling helps prevent resource exhaustion attacks by efficiently managing database connections.
These security measures work together to create a robust and secure web application. However, security is an ongoing process, and it's important to regularly review and update security practices as new threats emerge and best practices evolve.
To run the Rust web server, follow these steps:
-
Ensure that your database is set up and running as described in the Database Setup section.
-
Make sure you are in the project root directory.
-
Run the application using the following command:
cargo run -
The server will start and listen on
http://127.0.0.1:8080by default. -
You can now send requests to the API endpoints using tools like curl, Postman, or any HTTP client.
Example API requests:
-
Register a new user:
curl -X POST -H "Content-Type: application/json" -d "{\"username\":\"newuser\",\"email\":\"newuser@example.com\",\"password\":\"password123\"}" http://localhost:8080/api/auth/register -
Login:
curl -X POST -H "Content-Type: application/json" -d "{\"username\":\"newuser\",\"password\":\"password123\"}" http://localhost:8080/api/auth/login
Remember to replace the example values with actual data when testing the API.
If you encounter any issues while setting up or running the project, please check the following:
- Ensure that PostgreSQL is installed and running correctly on your system.
- Verify that the database connection details in your
.envfile are correct. - Make sure you have the latest version of Rust installed (
rustc --version). - If you're having issues with Diesel CLI, try reinstalling it with
cargo install diesel_cli --no-default-features --features postgres.
If you continue to experience problems, please open an issue on the project's GitHub repository with a detailed description of the error and the steps to reproduce it.