Розробка бекенду сайту на Rust (Actix Web)

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.
Розробка та обслуговування будь-яких видів сайтів:
Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Пропоновані послуги
Показано 1 з 1 послугУсі 2065 послуг
Розробка бекенду сайту на Rust (Actix Web)
Складна
від 2 тижнів до 3 місяців
Часті питання
Наші компетенції:
Етапи розробки
Останні роботи
  • image_website-b2b-advance_0.png
    Розробка сайту компанії B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    874
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Розробка веб-сайту для компанії ФІКСПЕР
    851

Розробка бекенду сайту на Rust (Actix Web)

Actix Web — один із найшвидших HTTP-фреймворків у існуючих бенчмарках. На TechEmpower benchmark він стабільно в першій п'ятірці серед всіх мов і фреймворків. Платня за продуктивність — більш високий поріг входу: ownership, lifetimes, async Rust — це не те, що освоюється за вихідні.

Коли вибирають Actix Web

Сервіси з жорсткими вимогами до latency (< 1ms p99), обробка фінансових транзакцій, високонагружені API-шлюзи, інфраструктурні компоненти — це територія Rust. Також: коли потрібна передбачуваність споживання пам'яті без GC-пауз, або коли сервіс працює в embedded/edge-середовищі з обмеженими ресурсами.

Структура додатку

// main.rs
use actix_web::{middleware, web, App, HttpServer};
use sqlx::PgPool;

mod config;
mod db;
mod errors;
mod handlers;
mod models;
mod services;

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    dotenvy::dotenv().ok();
    tracing_subscriber::fmt()
        .with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
        .init();

    let cfg = config::Config::from_env().expect("invalid config");
    let pool = PgPool::connect(&cfg.database_url).await.expect("db connect failed");
    sqlx::migrate!("./migrations").run(&pool).await.expect("migration failed");

    let pool = web::Data::new(pool);

    HttpServer::new(move || {
        App::new()
            .app_data(pool.clone())
            .app_data(web::JsonConfig::default().error_handler(errors::json_error_handler))
            .wrap(middleware::Logger::default())
            .wrap(middleware::Compress::default())
            .service(
                web::scope("/api/v1")
                    .service(handlers::users::scope())
                    .service(handlers::orders::scope()),
            )
    })
    .bind(("0.0.0.0", cfg.port))?
    .workers(num_cpus::get())
    .run()
    .await
}

Моделі та запити до БД через sqlx

sqlx перевіряє SQL-запити під час компіляції — опечатки та невідповідність типів стають помилками збірки, а не runtime-паніками:

// models/user.rs
use serde::{Deserialize, Serialize};
use sqlx::FromRow;
use time::OffsetDateTime;
use uuid::Uuid;

#[derive(Debug, Serialize, FromRow)]
pub struct User {
    pub id: Uuid,
    pub email: String,
    pub display_name: String,
    #[serde(skip)]
    pub password_hash: String,
    pub created_at: OffsetDateTime,
}

#[derive(Debug, Deserialize)]
pub struct CreateUserPayload {
    pub email: String,
    pub display_name: String,
    pub password: String,
}

// db/users.rs
pub async fn find_by_id(pool: &PgPool, id: Uuid) -> sqlx::Result<Option<User>> {
    sqlx::query_as!(
        User,
        r#"
        SELECT id, email, display_name, password_hash, created_at
        FROM users
        WHERE id = $1
        "#,
        id
    )
    .fetch_optional(pool)
    .await
}

pub async fn create(pool: &PgPool, payload: &CreateUserPayload) -> sqlx::Result<User> {
    let hash = bcrypt::hash(&payload.password, bcrypt::DEFAULT_COST).unwrap();
    sqlx::query_as!(
        User,
        r#"
        INSERT INTO users (id, email, display_name, password_hash)
        VALUES ($1, $2, $3, $4)
        RETURNING *
        "#,
        Uuid::new_v4(),
        payload.email,
        payload.display_name,
        hash
    )
    .fetch_one(pool)
    .await
}

Обробники та маршрутизація

// handlers/users.rs
use actix_web::{get, post, web, HttpResponse, Scope};
use sqlx::PgPool;
use uuid::Uuid;

use crate::{db, errors::AppError, models::user::CreateUserPayload};

pub fn scope() -> Scope {
    web::scope("/users")
        .service(get_user)
        .service(create_user)
}

#[get("/{id}")]
async fn get_user(
    pool: web::Data<PgPool>,
    id: web::Path<Uuid>,
) -> Result<HttpResponse, AppError> {
    let user = db::users::find_by_id(&pool, *id)
        .await?
        .ok_or(AppError::NotFound("user not found".into()))?;

    Ok(HttpResponse::Ok().json(user))
}

#[post("")]
async fn create_user(
    pool: web::Data<PgPool>,
    payload: web::Json<CreateUserPayload>,
) -> Result<HttpResponse, AppError> {
    let user = db::users::create(&pool, &payload).await?;
    Ok(HttpResponse::Created().json(user))
}

Обробка помилок

// errors.rs
use actix_web::{HttpResponse, ResponseError};
use serde_json::json;

#[derive(Debug, thiserror::Error)]
pub enum AppError {
    #[error("not found: {0}")]
    NotFound(String),
    #[error("validation error: {0}")]
    Validation(String),
    #[error("database error")]
    Database(#[from] sqlx::Error),
    #[error("unauthorized")]
    Unauthorized,
}

impl ResponseError for AppError {
    fn error_response(&self) -> HttpResponse {
        match self {
            AppError::NotFound(msg) => HttpResponse::NotFound().json(json!({ "error": msg })),
            AppError::Validation(msg) => {
                HttpResponse::UnprocessableEntity().json(json!({ "error": msg }))
            }
            AppError::Unauthorized => HttpResponse::Unauthorized().json(json!({ "error": "unauthorized" })),
            AppError::Database(e) => {
                tracing::error!("db error: {:?}", e);
                HttpResponse::InternalServerError().json(json!({ "error": "internal error" }))
            }
        }
    }
}

Middleware аутентифікації

// middleware/auth.rs
use actix_web::{dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform}, Error};
use futures_util::future::{ok, LocalBoxFuture, Ready};
use jsonwebtoken::{decode, DecodingKey, Validation};

pub struct JwtAuth;

impl<S, B> Transform<S, ServiceRequest> for JwtAuth
where
    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
    S::Future: 'static,
    B: 'static,
{
    // ... стандартна реалізація Transform
    fn new_transform(&self, service: S) -> Self::Future {
        ok(JwtAuthMiddleware { service })
    }
}

Cargo.toml залежності

[dependencies]
actix-web = "4"
sqlx = { version = "0.8", features = ["postgres", "uuid", "time", "runtime-tokio"] }
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
uuid = { version = "1", features = ["v4", "serde"] }
time = { version = "0.3", features = ["serde"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
jsonwebtoken = "9"
bcrypt = "0.15"
thiserror = "1"
dotenvy = "0.15"
num_cpus = "1"

Деплой

Фінальний бінарник — 5–15 МБ, без рантайму. Мінімальний Docker-образ:

FROM rust:1.77-slim AS builder
WORKDIR /app
COPY . .
RUN cargo build --release

FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/*
COPY --from=builder /app/target/release/myapi /usr/local/bin/
CMD ["myapi"]

Або scratch-образ якщо немає динамічних залежностей:

FROM scratch
COPY --from=builder /app/target/x86_64-unknown-linux-musl/release/myapi /
CMD ["/myapi"]

Графік розробки

Actix Web вимагає більше часу на розробку, ніж Rails або Node.js. Простий CRUD API (5–8 ресурсів): 2–3 тижні з урахуванням налаштування інфраструктури і тестів. Високонагружений сервіс з кастомними middleware, connection pool tuning та нагрузочним тестуванням: 4–7 тижнів. Час розробки компенсується операційними видатками: один інстанс Actix заміняє 5–10 Node.js-сервісів під аналогічною навантаженням.