Rust Web API
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
Server
For the server I am going to use actix_web. 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
Setup
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();
Migration
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]
Entities
We can now generate the entities we want to use in our code. 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"
Project Cargo Changes
The migration and the entities are libraries so we need to reference them in the root Cargo project we do this with the workspace and adding them as a dependency
...
[workspace]
members = [".", "entity", "migration"]
[dependencies]
entity = { path = "entity" }
migration = { path = "migration" } # depends on your needs
sea-orm = { version = "1.1.3", features = [
"sqlx-postgres",
"runtime-tokio-rustls",
"macros",
] }
...