$ cargo new axum_crud_api
Cargo.toml
[package]
name = "axum_crud_api"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
axum = "0.5.9"
tokio = { version="1.0", features = ["full"] }
serde = "1.0.137"
tracing = "0.1"
tracing-subscriber = { version="0.3", features = ["env-filter"] }
sqlx = { version = "0.5", features = ["runtime-tokio-tls", "json", "postgres"] }
anyhow = "1.0.58"
serde_json = "1.0.57"
tower-http = { version="0.3.4", features = ["trace"] }
main.rs
use axum::{
routing::{get},
Router,
};
use std::net::SocketAddr;
#[tokio::main]
async fn main() {
let app = Router::new()
.route("/", get(root));
let addr = SocketAddr::from(([192,168,56,10], 8000));
println!("listening on {}", addr);
axum::Server::bind(&addr)
.serve(app.into_make_service())
.await
.unwrap();
}
async fn root() -> &'static str {
"Hello, World"
}
task.rs
use serde::{Deserialize, Serialize};
pub struct Task {
pub id: i32,
pub task: String,
}
pub struct NewTask {
pub task: String,
}
DATABASE_URL = postgresql://user:password@locahost:host/database
$ cargo install sqlx-cli
$ cargo install sqlx-cli –no-default-features –features native-tls,postgres
$ cargo install sqlx-cli –features openssl-vendored
$ cargo install sqlx-cli –no-default-features –features rustls
sqlx database create
sqlx migrate add task
CREATE TABLE task {
id SERIAL PRIMARY KEY,
task varch(255) NOT NULL
}
sqlx migrate run
main.rs
use axum::{
extract::{Extension},
routing::{get, post},
Router,
};
use sqlx::postgres::PgPoolOptions;
use std::net::SocketAddr;
use std::fs;
use anyhow::Context
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let env = fs::read_to_string(".env").unwrap();
let (key, database_url) = env.split_once('=').unwrap();
assert_eq!(key, "DATABASE_URL");
tracing_subscriber::fmt::init();
let pool = PgPoolOptions::new()
.max_connections(50)
.connect(&dtabase_url)
.await
.context("could not connect to database_url")?;
let app = Router::new()
.route("/hello", get(root));
let addr = SocketAddr::from(([192,168,56,10], 8000));
println!("listening on {}", addr);
axum::Server::bind(&addr)
.serve(app.into_make_service())
.await?;
Ok(())
}
controller/task.rs
use axum::response::IntoResponse;
use axum::http::StatusCode;
use axum::{Extension, Json}
use sqlx::PgPool;
use crate::{
models::task
};
pub async fn all_tasks(Extension(pool): Extension<PgPool>) -> impl IntoResponse {
let sql = "SELECT * FROM task".to_string();
let task = sqlx::query_as::<_, task::Task>(&sql).fetch_all(&pool).await.unwrap();
(StatusCode::OK, Json(task))
}
error.rs
use axum::{http::StatusCode, response::IntoResponse, Json};
use serde_json::json;
pub enum CustomError {
BadRequest,
TaskNotFound,
InternalServerError,
}
impl IntoResponse for CustomError {
fn into_response(self) -> axum::response::Response {
let (status, error_message) = match self {
Self::InternalServerError => (
StatusCode::INTERNAL_SERVER_ERROR,
"Internal Server Error",
),
Self::BadRequest=> (StatusCode::BAD_REQUEST, "Bad Request"),
Self::TaskNotFound => (StatusCode::NOT_FOUND, "Task Not Found"),
};
(status, Json(json!({"error": error_message}))).into_response()
}
}
GET
// GET
pub async fn task(Path(id):Path<i32>,
Extension(pool): Extension<PgPool>)-> Result<Json<task::Task>, CustomError>{
let sql = "SELECT * FROM task where id=$1".to_string();
let task: task::Task = sqlx::query_as(&sql)
.bind(id)
.fetch_one(&pool)
.await
.map_err(|_| {
CustomError::TaskNotFound
})?;
Ok(Json(task))
}
// POST
pub async fn new_task(Json(task): Json<task::NewTask>,
Extension(pool): Extension<PgPool>) -> Result<(StatusCode,
Json<task::NewTask>), CustomError> {
if task.task.is_empty() {
return Err(CustomError::BadRequest)
}
let sql = "INSERT INTO task (task) values ($1)";
let _ = sql::query(&sql)
.bind(&task.task)
.execute(&pool)
.await
.map_err(|_| {
CustomError::InternalServerError
})?;
Ok((StatusCode::CREATED, Json(task)))
}
// PUT
pub async fn update_task(Path(id): Path<i32>,
Json(task): Json<task::UpdateTask>, Extension(pool): Extension<PgPool>)
-> Result <(StatusCode, Json<task::UpdateTask>), CustomError> {
let sql = "SELECT * FROM task where id=$1".to_string();
let _find: task::Task = sqlx::query_as(&sql)
.bind(id)
.fetch_one(&pool)
.await
.map_err(|_| {
CustomError::TaskNotFound
})?;
sqlx::query("UPDATE task SET task=$1 WHERE id=$2")
.bind(&task.task)
.bind(id)
.execute(&pool)
.await;
Ok((StatusCode::OK, Json(task)))
}
pub async fn delete_task(Path(id): Path<i32>, Extension(pool):Extension<PgPool>)
-> Result<(StatusCode, Json<Value>), CustomError> {
let _find: task::Task = sqlx::query_as("SELECT * FROM task where id=$1")
.bind(id)
.fetch_one(&pool)
.await
.map_err(|_| {
CustomError::TaskNotFound
})?;
sqlx::query("DELETE FROM task WHERE id=$1")
.bind(id)
.execute(&pool)
.await
.map_err(|_|{
CustomError::TaskNotFound
})?;
Ok((StatusCode::OK, Json(json!({"msg": "TaskDeleted"}))))
}
main.rs
use axum::{
extract::{Extension},
routing::{get, post, put, delete},
Router,
};
use sqlx::postgres::PgPoolOptions;
use std::net::SocketAddr;
use std::fs;
use anyhow::Context
use tower_http::trace::TraceLayer;
use tracing_subscriber::{layer::SubscriberExt, util::SuscriberInitExt};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let env = fs::read_to_string(".env").unwrap();
let (key, database_url) = env.split_once('=').unwrap();
assert_eq!(key, "DATABASE_URL");
tracing_subscriber::registery()
.with(tracing_subscriber::EnvFilter::new(
std::env::var("tower_http=trace")
.unwrap_or_else(|_| "example_tracing_aka_logging=debug,tower_http=debug".into()),
))
.with(tracing_subscriber::fmt::layer())
.init();
let pool = PgPoolOptions::new()
.max_connections(50)
.connect(&dtabase_url)
.await
.context("could not connect to database_url")?;
let app = Router::new()
.route("/hello", get(root))
.route("/tasks", get(controllers::task::all_tasks))
.route("/task", post(controllers::task::new_task))
.route("/task/:id", get(controllers::task::task))
.route("/task/:id", put(controllers::task::update_task))
.route("/task/:id", delete(controllers::task::delete_task))
.layer(Extension(pool))
.layer(TraceLayer::new_for_http());
let addr = SocketAddr::from(([192,168,56,10], 8000));
println!("listening on {}", addr);
axum::Server::bind(&addr)
.serve(app.into_make_service())
.await?;
Ok(())
}
async fn root() -> &'static str {
"Hello, World"
}