Building a Rust/Axum Server
TypoKit’s @typokit/plugin-axum generates a complete, production-ready Rust/Axum web server from your TypeScript schema types and route contracts. The same type definitions that drive your Node.js API can produce a high-performance Rust backend — no manual translation required.
What You’ll Build
Section titled “What You’ll Build”A Todo API server written in Rust with:
- Axum for HTTP routing and handler dispatch
- sqlx for type-safe PostgreSQL queries
- serde for JSON serialization/deserialization
- validator for request body validation
- Auto-generated models, router, database layer, and migrations — all from TypeScript types
Prerequisites
Section titled “Prerequisites”| Tool | Version | Purpose |
|---|---|---|
| Node.js | 24+ | TypoKit CLI and build pipeline |
| pnpm | Latest | Package manager |
| Rust | 1.85+ | Compilation target (edition 2024) |
| PostgreSQL | 16+ | Database backend |
Optional but recommended:
# sqlx CLI for running migrationscargo install sqlx-cli --no-default-features --features postgresStep 1 — Define Your Schema Types
Section titled “Step 1 — Define Your Schema Types”Start with your TypeScript type definitions. These are the single source of truth for the entire Rust server.
-
Create a types directory with your entity interfaces:
types/user.ts /** @table users */export interface User {/** @id @generated uuid */id: string;/** @format email @unique @maxLength 255 */email: string;/** @minLength 2 @maxLength 100 */displayName: string;/** @default "active" */status: "active" | "suspended" | "deleted";/** @generated now */createdAt: Date;/** @generated now */updatedAt: Date;}export type CreateUserInput = Omit<User, "id" | "createdAt" | "updatedAt">;export type UpdateUserInput = Partial<CreateUserInput>;types/todo.ts /** @table todos */export interface Todo {/** @id @generated uuid */id: string;/** @minLength 1 @maxLength 200 */title: string;completed: boolean;/** References User.id */userId: string;/** @generated now */createdAt: Date;/** @generated now */updatedAt: Date;}export type CreateTodoInput = Omit<Todo, "id" | "createdAt" | "updatedAt">;export type UpdateTodoInput = Partial<Omit<CreateTodoInput, "userId">>; -
Create route contracts that define your API surface:
routes/users.ts import type { RouteContract } from "@typokit/types";import type { User, CreateUserInput, UpdateUserInput } from "../types/user";export interface UserRoutes {"GET /users": RouteContract<void, void, void, User[]>;"POST /users": RouteContract<void, void, CreateUserInput, User>;"GET /users/:id": RouteContract<{ id: string }, void, void, User>;"PUT /users/:id": RouteContract<{ id: string }, void, UpdateUserInput, User>;"DELETE /users/:id": RouteContract<{ id: string }, void, void, void>;}routes/todos.ts import type { RouteContract } from "@typokit/types";import type { Todo, CreateTodoInput, UpdateTodoInput } from "../types/todo";export interface TodoRoutes {"GET /todos": RouteContract<void, void, void, Todo[]>;"POST /todos": RouteContract<void, void, CreateTodoInput, Todo>;"GET /todos/:id": RouteContract<{ id: string }, void, void, Todo>;"PUT /todos/:id": RouteContract<{ id: string }, void, UpdateTodoInput, Todo>;"DELETE /todos/:id": RouteContract<{ id: string }, void, void, void>;}
Step 2 — Configure the Axum Plugin
Section titled “Step 2 — Configure the Axum Plugin”Install the plugin and configure it in your project:
-
Install the plugin:
Terminal window pnpm add @typokit/plugin-axum -
Create or update
typokit.config.ts:import { axumPlugin } from "@typokit/plugin-axum";export default {plugins: [axumPlugin({ db: "sqlx" })],};
Step 3 — Generate the Rust Server
Section titled “Step 3 — Generate the Rust Server”Run the TypoKit build to generate all Rust source files:
typokit buildYou should see output like:
● Resolving type files... 2 files found● Resolving route files... 2 files found● Running transform... done in 98ms● Plugin: emit (plugin-axum) 18 files generated● Plugin: compile (plugin-axum) Running: cargo build Compiling todo-server v0.1.0● Build complete (12.4s)Step 4 — Understand the Generated Output
Section titled “Step 4 — Understand the Generated Output”After the build, your project has this structure:
.typokit/ ← Auto-generated (always overwritten) models/ user.rs ← User struct with serde + validator + sqlx derives todo.rs ← Todo struct mod.rs ← Module re-exports db/ mod.rs ← PgPool connection + CRUD functions for all entities router.rs ← Axum Router with all route registrations app.rs ← AppState struct (shared PgPool) error.rs ← AppError enum → HTTP status code mapping migrations/ 000000000001_create_users.sql 000000000002_create_todos.sql
src/ ← User code (never overwritten) handlers/ users.rs ← Handler stubs for User routes todos.rs ← Handler stubs for Todo routes mod.rs ← Module re-exports services/ users.rs ← Business logic stubs todos.rs mod.rs middleware/ mod.rs ← Auth middleware stub main.rs ← Tokio async entrypoint lib.rs ← Module bridge (#[path] to .typokit/)
Cargo.toml ← Project manifestThe Overwrite Model
Section titled “The Overwrite Model”Understanding which files are safe to edit is critical:
| Location | Overwrite Behavior | What To Do |
|---|---|---|
.typokit/** | ✅ Always overwritten | Never edit — modify your TypeScript types instead |
src/handlers/** | ❌ Never overwritten | Write your handler implementations here |
src/services/** | ❌ Never overwritten | Write your business logic here |
src/middleware/** | ❌ Never overwritten | Write your middleware here |
src/main.rs | ✅ Overwritten | Customize startup in src/lib.rs instead |
src/lib.rs | ✅ Overwritten | Module bridge — auto-managed |
Cargo.toml | ✅ Overwritten | Add extra deps after initial generation |
Step 5 — Set Up the Database
Section titled “Step 5 — Set Up the Database”-
Create the PostgreSQL database:
Terminal window createdb typokit_todo -
Set the connection string:
Terminal window export DATABASE_URL=postgresql://localhost/typokit_todoOr create a
.envfile:DATABASE_URL=postgresql://localhost/typokit_todo -
Run the generated migrations:
Terminal window psql $DATABASE_URL -f .typokit/migrations/000000000001_create_users.sqlpsql $DATABASE_URL -f .typokit/migrations/000000000002_create_todos.sqlOr with sqlx-cli:
Terminal window sqlx migrate run --source .typokit/migrations
Step 6 — Implement Your Handlers
Section titled “Step 6 — Implement Your Handlers”The generated handler stubs in src/handlers/ contain TODO placeholders. Fill them in with your application logic:
use axum::{extract::{Path, State}, Json};use uuid::Uuid;
use crate::app::AppState;use crate::error::AppError;use crate::models::user::{CreateUserInput, User};use crate::services;
pub async fn list_users( State(state): State<AppState>,) -> Result<Json<Vec<User>>, AppError> { let users = services::users::list(&state.pool).await?; Ok(Json(users))}
pub async fn create_user( State(state): State<AppState>, Json(input): Json<CreateUserInput>,) -> Result<Json<User>, AppError> { let user = services::users::create(&state.pool, input).await?; Ok(Json(user))}
pub async fn get_user( State(state): State<AppState>, Path(id): Path<Uuid>,) -> Result<Json<User>, AppError> { let user = services::users::find_by_id(&state.pool, id).await?; Ok(Json(user))}Step 7 — Build and Run
Section titled “Step 7 — Build and Run”cargo buildcargo runThe server starts on http://localhost:3000. Test it:
# Create a usercurl -X POST http://localhost:3000/users \ -H "Content-Type: application/json" \ -d '{"email": "alice@example.com", "displayName": "Alice", "status": "active"}'
# List userscurl http://localhost:3000/users
# Get a user by IDcurl http://localhost:3000/users/<id>How It Works Under the Hood
Section titled “How It Works Under the Hood”The @typokit/plugin-axum plugin hooks into two phases of the TypoKit build pipeline:
-
emithook — The plugin’s native Rust addon parses your TypeScript types and route contracts, then generates Rust source files. This uses the same@typokit/transform-nativeAST parser that powers the standard TypeScript pipeline. -
compilehook — Instead of runningtsc, the plugin runscargo build. It setscompileCtx.handled = trueso the default TypeScript compiler is skipped entirely.
TypeScript Types + Route Contracts │ ▼┌────────────────────────┐│ TypoKit Build Pipeline ││ ││ 1. Parse TS ASTs │ ← Rust (swc via napi-rs)│ 2. Extract metadata ││ 3. Route compilation ││ 4. Plugin: emit │ ← plugin-axum generates Rust files│ 5. Plugin: compile │ ← plugin-axum runs `cargo build`└────────────────────────┘ │ ▼ Running Axum ServerContent-Hash Caching
Section titled “Content-Hash Caching”The plugin uses SHA-256 content hashing for incremental builds. If your type and route files haven’t changed since the last build, the Rust code generation step is skipped entirely. The cache hash is stored at .typokit/.cache-hash.
Reference Example
Section titled “Reference Example”For a complete, working implementation, see the example-todo-server-axum package in the TypoKit monorepo.
Next Steps
Section titled “Next Steps”- Plugins — @typokit/plugin-axum — Full plugin documentation and configuration reference
- Build Pipeline — How the build pipeline and
compilehook work - Custom Plugin Development — Build your own plugin that targets a different language