Debug School

rakesh kumar
rakesh kumar

Posted on

Build Secure User Registration in Actix-Web (Rust) with Database Integration – Production Ready Guide

Introduction

In any secure backend system, authentication starts with user registration. Before users can log in, their credentials must be safely stored in the database. A properly built registration system ensures:

Secure password handling

Clean API design

Scalable architecture

Protection against common attacks
Enter fullscreen mode Exit fullscreen mode

In this guide, we will build a production-ready /api/register endpoint using:

Actix-Web

MySQL (via SQLx)

Clean modular architecture

JSON API responses
Enter fullscreen mode Exit fullscreen mode

2️⃣ Why Registration Endpoint Is Important
What It Does

The registration endpoint creates new user records in the database.

Why It Matters

Without registration:

Login has nothing to validate

Session system becomes meaningless

Admin panel cannot manage users
Enter fullscreen mode Exit fullscreen mode

When You Need It

Admin dashboards

Vendor systems

SaaS platforms

Multi-tenant applications
Enter fullscreen mode Exit fullscreen mode

3️⃣ Project Structure (Clean Architecture)

We keep separation of concerns:

src/
 ├── main.rs
 ├── db/
 │     └── user.rs
 ├── handlers/
 │     └── auth.rs
 ├── models.rs
 └── common.rs
Enter fullscreen mode Exit fullscreen mode

This keeps:

DB logic in db/

Route logic in handlers/

Struct definitions in models.rs
Enter fullscreen mode Exit fullscreen mode

4️⃣ Step 1: Create Register Model

File: src/models.rs

use serde::{Deserialize, Serialize};

#[derive(Debug, Deserialize)]
pub struct RegisterForm {
    pub username: String,
    pub password: String,
    pub role: Option<String>,
}
Enter fullscreen mode Exit fullscreen mode

Why We Use Deserialize

Because Actix automatically converts incoming JSON body into this struct.

5️⃣ Step 2: Create Database Insert Function

File: src/db/user.rs

use sqlx::MySqlPool;

pub async fn create_user(
    pool: &MySqlPool,
    username: &str,
    password_hash: &str,
    role: &str,
) -> Result<u64, sqlx::Error> {
    let result = sqlx::query(
        "INSERT INTO users (username, password, role) VALUES (?, ?, ?)"
    )
    .bind(username)
    .bind(password_hash)
    .bind(role)
    .execute(pool)
    .await?;

    Ok(result.last_insert_id())
}
Enter fullscreen mode Exit fullscreen mode

Why Separate DB Logic?

Keeps handlers clean and reusable.

6️⃣ Step 3: Create Register Handler

File: src/handlers/auth.rs

use actix_web::{error, web, HttpResponse, Result};
use crate::db::create_user;
use crate::models::RegisterForm;
use super::common::ApiResponse;

pub async fn register(
    body: web::Json<RegisterForm>,
    state: web::Data<AppState>,
) -> Result<HttpResponse> {

    let role = body.role.clone().unwrap_or_else(|| "viewer".to_string());

    let id = create_user(
        &state.pool,
        &body.username,
        &body.password,
        &role,
    )
    .await
    .map_err(error::ErrorInternalServerError)?;

    Ok(HttpResponse::Ok().json(ApiResponse {
        message: "User created successfully".to_string(),
        data: id,
    }))
}
Enter fullscreen mode Exit fullscreen mode

7️⃣ Step 4: Add Route in main.rs

Inside your scope:

web::scope("/api")
    .route("/register", web::post().to(handlers::register))
Enter fullscreen mode Exit fullscreen mode

Now endpoint becomes:

POST /api/register
Enter fullscreen mode Exit fullscreen mode

8️⃣ Step 5: Test Using Postman

Request:

POST http://localhost:8080/api/register
Enter fullscreen mode Exit fullscreen mode

Body:

{
  "username": "ashwani",
  "password": "123456",
  "role": "admin"
}
Enter fullscreen mode Exit fullscreen mode

Response:

{
  "message": "User created successfully",
  "data": 12
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)