| Crates.io | netron_axum_ts |
| lib.rs | netron_axum_ts |
| version | 0.0.2 |
| created_at | 2025-11-27 06:42:59.826652+00 |
| updated_at | 2025-11-27 06:44:39.723642+00 |
| description | Generate TypeScript TRPC client code from Axum Rust server code. |
| homepage | |
| repository | https://github.com/netrondev/netron-axum |
| max_upload_size | |
| id | 1953202 |
| size | 46,178 |
A code generator that automatically creates TypeScript tRPC routers from Rust #[axumhandler] route definitions.
This tool bridges Rust backend APIs (built with the netron framework) and TypeScript frontend applications (using tRPC), providing:
This is a standalone Rust crate with a CLI tool. Build it with:
cargo build --release
netron-axum-ts \
--input sfprs/src/api/products.rs \
--output sfpt3/src/server/api/routers/products.generated.ts \
--router-name productsRouter
Add to your package.json:
{
"scripts": {
"generate:api": "cd ../netron_axum_ts && cargo run --release -- -i ../sfprs/src/api/products.rs -o ../sfpt3/src/server/api/routers/products.generated.ts --router-name productsRouter"
}
}
Then run:
pnpm generate:api
The tool parses Rust files looking for functions annotated with #[axumhandler]:
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
struct Brand {
brand_name: String,
}
#[axumhandler(method = "GET", path = "/api/brands")]
pub async fn brandlist() -> Result<Vec<Brand>, AppError> {
// Implementation
}
#[axumhandler(path = "/api/models")]
pub async fn modellist(brand: Brand) -> Result<Vec<String>, AppError> {
// Implementation
}
Generates a fully-typed tRPC router:
// Auto-generated
import { z } from "zod";
import { createTRPCRouter, publicProcedure } from "~/server/api/trpc";
const RUST_API_URL = process.env.RUST_API_URL || "http://localhost:4000";
export const BrandSchema = z.object({
brand_name: z.string(),
});
export type Brand = z.infer<typeof BrandSchema>;
export const productsRouter = createTRPCRouter({
brandlist: publicProcedure
.input(z.void())
.query(async () => {
const response = await fetch(`${RUST_API_URL}/api/brands`);
if (!response.ok) throw new Error('API request failed');
return BrandSchema.array().parse(await response.json());
}),
modellist: publicProcedure
.input(BrandSchema)
.mutation(async ({ input }) => {
const response = await fetch(`${RUST_API_URL}/api/models`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(input),
});
if (!response.ok) throw new Error('API request failed');
return z.string().array().parse(await response.json());
}),
});
In your Next.js application:
// Server-side
const brands = await api.products.brandlist();
// Client-side
const { data: brands } = api.products.brandlist.useQuery();
const modelsMutation = api.products.modellist.useMutation();
| Rust Type | Zod Schema | TypeScript Type |
|---|---|---|
String |
z.string() |
string |
i32, u32, f64 |
z.number() |
number |
bool |
z.boolean() |
boolean |
Vec<T> |
z.array(T) |
T[] |
Option<T> |
z.nullable(T) |
T | null |
Result<T, E> |
(unwrapped to T) |
T |
| Custom structs | z.object({...}) |
interface |
Handles #[serde(rename = "...")]:
struct Product {
#[serde(rename = "type")]
product_type: String, // Becomes "type" in JSON/TypeScript
}
GET → .query() (read-only)POST/PUT/DELETE → .mutation() (state-changing)Set the backend URL via environment variable:
# .env
RUST_API_URL=http://localhost:4000
netron-axum-ts [OPTIONS]
Options:
-i, --input <FILE> Input Rust source file
-o, --output <FILE> Output TypeScript file
--api-url <URL> Backend URL [default: http://localhost:4000]
--router-name <NAME> Router name [default: derived from filename]
-h, --help Print help
-V, --version Print version
#[axumhandler]Serialize and DeserializeVec, Option, Result are not supportedRun tests:
cargo test
Build for release:
cargo build --release
Same as parent project.