Rust Web API

From bibbleWiki
Revision as of 23:13, 9 January 2025 by Iwiseman (talk | contribs) (Database)
Jump to navigation Jump to search

Introduction

This page is to point me in the right direction should someone want to do this in rust. I think that Golang, C# or NodeJS (to reduce technology) might be a better choice. This language seems a lot harder to learn. Maybe if you already use it for other stuff in you company. Doing this I was surprise how easy it was. Maybe microcontrollers have clouded my judgement

actix_web

Server

I am sure there are many choices knowing rust but this is the first one I came across. May rewrite this as we go but here we are

    HttpServer::new(move || {
        App::new()
            .app_data(repository::AppState::new(db.clone()))
            .wrap(Logger::default())
            .configure(routes::home_routes::config) // Better Approach
            .service(hello) // Sample route 1
            .service(echo)  // Sample route 2 
            .route("/hey", web::get().to(manual_hello))
    })
    .bind((address, port))?
    .run()
    .await

Routes

Here is the example for the routes. We will need to look at middleware no doubt

pub fn config(config: &mut web::ServiceConfig) {
    config.service(
        web::scope("/home")
            .service(handlers::home_handlers::greet)
            .service(handlers::home_handlers::test),
    );
}

Handlers

And here are the handlers which I will hopefully update to have your 401, 404 etc

use actix_web::{get, web, Responder};

#[get("/greet/{name}")]
pub async fn greet(name: web::Path<String>) -> impl Responder {
    format!("Hello {name}!",)
}

#[get("/test")]
pub async fn test() -> impl Responder {
    format!("Hello world!")
}

Database

Well this is only meant to be brief as there are many ways to solve this. Here we are with the database bit. They use sea-orm, and you know how I feel about orms. So for postgress we add

sea-orm = { version = "1.1.3", features = [
    "sqlx-postgres",
    "runtime-tokio-rustls",
    "macros",
] }

Create a database and user. Then you can make a connection. Note migrations rely on DATABASE_URL so you may want to consider this. I have put the Migrator script to run before creating the server. We will need to complete migration before this will run again

    let database_url = format!(
        "postgres://{}:{}@{}:{}/{}",
        std::env::var("DB_USER").unwrap(),
        std::env::var("DB_PASSWORD").unwrap(),
        std::env::var("DB_HOST").unwrap(),
        std::env::var("DB_PORT").unwrap(),
        std::env::var("DB_NAME").unwrap(),
    );

    let db = Database::connect(&database_url).await.unwrap();

    Migrator::up(&db, None).await.unwrap();

We can now use the sea-orm-cli to make a migration script

cargo install sea-orm-cli
sea-orm-cli migrate init
sea-orm-cli migrate generate create_user_table

The first command creates a separate project which we can create out migration scripts in. And the second one adds a script for our user table we amend this to our needs

use sea_orm_migration::prelude::*;

#[derive(DeriveMigrationName)]
pub struct Migration;

#[async_trait::async_trait]
impl MigrationTrait for Migration {
    async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
        manager
            .create_table(
                Table::create()
                    .table(User::Table)
                    .if_not_exists()
                    .col(
                        ColumnDef::new(User::Id)
                            .integer()
                            .not_null()
                            .auto_increment()
                            .primary_key(),
                    )
                    .col(ColumnDef::new(User::Name).string().not_null())
                    .col(ColumnDef::new(User::Email).string().not_null().unique_key())
                    .col(ColumnDef::new(User::Password).string().not_null())
                    .to_owned(),
            )
            .await
    }

    async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
        manager
            .drop_table(Table::drop().table(User::Table).to_owned())
            .await
    }
}

#[derive(DeriveIden)]
pub enum User {
    Table,
    Id,
    Name,
    Email,
    Password,
}

You will need to change the migration Cargo.toml so have the relevant drivers and then you can run the migration

sea-orm-cli migrate up

You can use up for migrating forward, down for backward and others see [options]
We can now generate the entity. This is done by the sea-orm-cli. I did find it a bit nasty that you have to create the Cargo.toml manually afterwards

sea-orm-cli generate entity -o entity/src

For the Cargo.toml we just add serde and sea-orm

[package]
name = "entity"
version = "0.1.0"
edition = "2021"
publish = false

[lib]
name = "entity"
path = "src/mod.rs"

[dependencies]
serde = { verison = "1.0.197", features = [ "derive" ] }

[dependencies.sea-orm]
version = "1.1.3"