Skip to Content
Genexy IDE Beta 0.9.1 · Download the Beta build
Features · Code generation

Rust (`axum`)

PROIDEExperimental

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

Optiongnx create flagDefaultNotes
crateName--cratethe id in kebab caseName of the main crate
rustVersion--rust1.941.88 or newer, 1.94 or newer with a database
basePathfrom the contractPath prefix of the API; otherwise taken from servers[0].url
dependenciesExtra crates as crate@version or crate@version[feature,...]
exposes--port, --contractone port, 8080Ports: see Ports, Middleware & Security
exposes[].middleware, .validation, .securityMiddleware, validation, authentication
modulesDatabases, 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-adapter with the SQL schema contract in contract (sqlx underneath). With several databases, mark one primary: true.
  • rest-client with an OpenAPI contract: a typed client of another API. Its URL is genexy.clients.<name>.url or the <NAME>_URL environment variable; headers such as credentials go into genexy.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>Service traits with #[async_trait]. Look up repositories, clients and other services in the new(inj) constructor with inj.get::<dyn Trait>() (inj.lazy::<dyn Trait>() for another business service). New modules under src/app/ are declared in src/app/mod.rs. Your code must not use axum, sqlx, reqwest or hyper directly; tests/architecture.rs fails if it does.
  • Errors: return platform::HttpError (HttpError::new(status, code, message), or HttpError::with_body to 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 with inj.get::<config::Config>().get::<T>("app"). Ports are set with genexy.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 run

Tests 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.