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
In this guide, we will build a production-ready /api/register endpoint using:
Actix-Web
MySQL (via SQLx)
Clean modular architecture
JSON API responses
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
When You Need It
Admin dashboards
Vendor systems
SaaS platforms
Multi-tenant applications
3️⃣ Project Structure (Clean Architecture)
We keep separation of concerns:
src/
├── main.rs
├── db/
│ └── user.rs
├── handlers/
│ └── auth.rs
├── models.rs
└── common.rs
This keeps:
DB logic in db/
Route logic in handlers/
Struct definitions in models.rs
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>,
}
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())
}
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,
}))
}
7️⃣ Step 4: Add Route in main.rs
Inside your scope:
web::scope("/api")
.route("/register", web::post().to(handlers::register))
Now endpoint becomes:
POST /api/register
8️⃣ Step 5: Test Using Postman
Request:
POST http://localhost:8080/api/register
Body:
{
"username": "ashwani",
"password": "123456",
"role": "admin"
}
Response:
{
"message": "User created successfully",
"data": 12
}
Top comments (0)