Step-by-Step: Set Up a Rust 1.85 Web Server with Actix 5.0 and PostgreSQL 17
This guide walks you through building a production-ready web server using Rust 1.85, the Actix 5.0 web framework, and PostgreSQL 17 as your database. We’ll cover project initialization, database integration, API endpoint creation, and testing.
Prerequisites
Ensure you have the following installed before starting:
- Rust 1.85 (install via rustup, then run
rustup install 1.85.0 && rustup default 1.85.0) - PostgreSQL 17 (follow official installation instructions for your OS)
- Cargo (comes with Rust)
- psql (PostgreSQL CLI, included with PostgreSQL 17)
Step 1: Set Up PostgreSQL 17 Database
First, create a dedicated database and user for your project:
sudo -u postgres psql
CREATE DATABASE rust_actix_db;
CREATE USER rust_user WITH PASSWORD 'secure_password_here';
GRANT ALL PRIVILEGES ON DATABASE rust_actix_db TO rust_user;
\q
Note: Replace secure_password_here with a strong password of your choice.
Step 2: Initialize Rust Project
Create a new Rust project using Cargo:
cargo new rust-actix-postgres-server
cd rust-actix-postgres-server
Step 3: Add Dependencies
Update your Cargo.toml to include Actix 5.0, SQLx for PostgreSQL integration, and dotenv for environment variables:
[package]
name = "rust-actix-postgres-server"
version = "0.1.0"
edition = "2021"
[dependencies]
actix-web = "5.0"
tokio = { version = "1.0", features = ["full"] }
sqlx = { version = "0.8", features = ["runtime-tokio-native-tls", "postgres", "macros", "chrono"] }
dotenv = "0.15"
chrono = { version = "0.4", features = ["serde"] }
Step 4: Configure Environment Variables
Create a .env file in the project root to store your database connection string:
DATABASE_URL=postgres://rust_user:secure_password_here@localhost/rust_actix_db
SERVER_ADDR=127.0.0.1:8080
Replace secure_password_here with the password you set in Step 1. Add .env to your .gitignore to avoid committing sensitive data.
Step 5: Set Up Database Connection Pool
Create a src/db.rs file to handle PostgreSQL connections using SQLx:
use sqlx::postgres::PgPoolOptions;
use std::time::Duration;
pub async fn init_pool() -> sqlx::PgPool {
let database_url = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set");
PgPoolOptions::new()
.max_connections(5)
.acquire_timeout(Duration::from_secs(3))
.connect(&database_url)
.await
.expect("Failed to connect to Postgres")
}
Step 6: Define Data Models
Create a src/models.rs file to define your data structures. We’ll use a simple User model for this guide:
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use sqlx::FromRow;
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
pub struct User {
pub id: i32,
pub username: String,
pub email: String,
pub created_at: DateTime,
}
#[derive(Debug, Deserialize)]
pub struct CreateUserRequest {
pub username: String,
pub email: String,
}
Step 7: Create Database Migrations (Optional but Recommended)
Install SQLx CLI to manage migrations:
cargo install sqlx-cli --features postgres
Create a migration to set up the users table:
sqlx migrate add create_users_table
Edit the generated migration file in src/migrations/YYYYMMDDHHMMSS_create_users_table.sql:
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
email VARCHAR(100) NOT NULL UNIQUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
Run the migration:
sqlx migrate run
Step 8: Write API Endpoints
Update src/main.rs to define your Actix 5.0 routes and handlers:
use actix_web::{web, App, HttpServer, Responder, HttpResponse};
use dotenv::dotenv;
use sqlx::PgPool;
mod db;
mod models;
use models::{User, CreateUserRequest};
// GET /users: Fetch all users
async fn get_users(pool: web::Data) -> impl Responder {
let result = sqlx::query_as::<_, User>("SELECT id, username, email, created_at FROM users")
.fetch_all(pool.get_ref())
.await;
match result {
Ok(users) => HttpResponse::Ok().json(users),
Err(e) => HttpResponse::InternalServerError().body(e.to_string()),
}
}
// POST /users: Create a new user
async fn create_user(
pool: web::Data,
req: web::Json,
) -> impl Responder {
let result = sqlx::query_as::<_, User>(
"INSERT INTO users (username, email) VALUES ($1, $2) RETURNING id, username, email, created_at"
)
.bind(&req.username)
.bind(&req.email)
.fetch_one(pool.get_ref())
.await;
match result {
Ok(user) => HttpResponse::Created().json(user),
Err(e) => HttpResponse::InternalServerError().body(e.to_string()),
}
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
dotenv().ok();
let pool = db::init_pool().await;
let server_addr = std::env::var("SERVER_ADDR").unwrap_or_else(|_| "127.0.0.1:8080".to_string());
println!("Starting server at http://{}", server_addr);
HttpServer::new(move || {
App::new()
.app_data(web::Data::new(pool.clone()))
.route("/users", web::get().to(get_users))
.route("/users", web::post().to(create_user))
})
.bind(server_addr)?
.run()
.await
}
Step 9: Test the Server
Build and run your server:
cargo run
You should see Starting server at http://127.0.0.1:8080. Test the endpoints with curl:
# Create a new user
curl -X POST http://127.0.0.1:8080/users \
-H "Content-Type: application/json" \
-d '{"username": "test_user", "email": "test@example.com"}'
# Fetch all users
curl http://127.0.0.1:8080/users
Conclusion
You’ve successfully set up a Rust 1.85 web server using Actix 5.0 and PostgreSQL 17. This foundation can be extended with additional endpoints, authentication, middleware, and deployment configurations for production use.
Top comments (0)