Rust (`axum`)
Generates a Rust backend as a Cargo workspace on axum and tokio: the main crate with your code, one crate per API contract, per database and per REST client, and the runtime crates. Crate versions are pinned, so the same spec always builds the same way.
Inputs
| Option | gnx create flag | Default | Notes |
|---|---|---|---|
crateName | --crate | the id in kebab case | Name of the main crate |
rustVersion | --rust | 1.94 | 1.88 or newer, 1.94 or newer with a database |
basePath | from the contract | Path prefix of the API; otherwise taken from servers[0].url | |
dependencies | Extra crates as crate@version or crate@version[feature,...] | ||
exposes | --port, --contract | one port, 8080 | Ports: see Ports, Middleware & Security |
exposes[].middleware, .validation, .security | Middleware, validation, authentication | ||
modules | Databases, REST clients and security modules, see below |
Contracts: OpenAPI 3.0 and 3.1 for exposes and for REST clients, SQL schema for databases.
Modules:
postgresql-adapterwith the SQL schema contract incontract(sqlx underneath). With several databases, mark oneprimary: true.rest-clientwith an OpenAPI contract: a typed client of another API. Its URL isgenexy.clients.<name>.urlor the<NAME>_URLenvironment variable; headers such as credentials go intogenexy.clients.<name>.headers.security: JWT / OIDC bearer tokens for the ports that use it, see Authentication.
MySQL is not generated for Rust yet; the generator skips it with a warning.
modules:
- name: main-db
type: postgresql-adapter
contract: { $ref: db/orders.yaml }
- name: payments
type: rest-client
contract: { $ref: ../payments/api/payments.openapi.yaml }OpenAPI support: schemas with enums, nullable, defaults and formats; path parameters (also several in one segment),
query, header and cookie parameters; JSON and multipart bodies. Not yet: oneOf, anyOf and discriminators, response
headers and several success statuses, readOnly/writeOnly, streaming.
Outputs
orders/
├─ .genexy/ component spec and the list of generated files
├─ Cargo.toml, build.rs generated (build.rs embeds the migrations)
├─ Cargo.lock yours, written by cargo on the first build
├─ compose.yaml with a database: PostgreSQL for local runs and tests
├─ Dockerfile, README.md yours
├─ config/application.yaml yours: your settings
├─ migrations/main-db/ yours: your own data migrations
├─ src/
│ ├─ main.rs, lib.rs generated
│ ├─ bootstrap/, ports/ generated: wiring, one server per port
│ └─ app/ yours
│ ├─ mod.rs stub
│ └─ orders/orders_service_impl.rs stub, one per OpenAPI tag
├─ tests/
│ ├─ architecture.rs generated: keeps src/app away from framework internals
│ └─ app.rs stub
└─ modules/ generated
├─ api-orders/ models, <Tag>Service traits, controllers
├─ db-main-db/ rows, repositories, queries, transactions, migrations/
├─ client-payments/ <Tag>Client traits
└─ platform/, inject/, config/, logger/- Your code goes into
src/app/: the stubs implement the async<Tag>Servicetraits with#[async_trait]. Look up repositories, clients and other services in thenew(inj)constructor withinj.get::<dyn Trait>()(inj.lazy::<dyn Trait>()for another business service). New modules undersrc/app/are declared insrc/app/mod.rs. Your code must not use axum, sqlx, reqwest or hyper directly;tests/architecture.rsfails if it does. - Errors: return
platform::HttpError(HttpError::new(status, code, message), orHttpError::with_bodyto send the error model of the contract). Other errors become RFC 9457 problem details; a database constraint violation becomes 409, a failed client call 502. - Database:
tx.in_transaction(async { … })runs repository calls in a transaction. The generator writes the next migration when the schema changes and never rewrites it. The migration history is compatible with the Go generator, so a Go and a Rust service can share a database. - Configuration:
config/application.yaml, then.env, then environment variables. Read your own settings withinj.get::<config::Config>().get::<T>("app"). Ports are set withgenexy.ports.<name>.port.
Ports, middleware and security
Every port has its own server and router; the services exist once for all ports. The middleware of a port is in
platform::web (axum middleware functions): request id, panic recovery (500), access log, security headers, CORS,
body limit, request timeout, authentication, rate limit, validation options and gzip. Each generated route carries
the security its contract requires; on a port with a security module it is checked by a route layer. All options:
Ports, Middleware & Security.
The tokens are verified by platform::security with ring, the JWKS is loaded with reqwest. These crates are
added to the workspace only when the component has a security module.
// In a service method: None for an anonymous request.
if let Some(caller) = platform::auth::principal() {
if !caller.has_role("admin") && row.owner != caller.subject {
return Err(platform::HttpError::new(403, "forbidden", "not your order").into());
}
}In tests, start the application on free ports with
App::builder().with_port("api", 0).with_config("genexy.security.auth.jwksUrl", url).build()?, then
app.start().await? and app.ports.url("api").
Build and run
Needs Rust 1.88 or newer (1.94 with a database). Building needs no running database. In the component folder:
cargo build
cargo clippy --all-targets
cargo test
cargo runTests that start the application with a database need PostgreSQL running, for example from the generated
compose.yaml with docker compose up -d, and GENEXY_TEST_DATABASES=external.