# Build your backend with Rust and Axum

A full-stack Forma application is an ordinary Rust web application with Forma
page rendering. You can create JSON APIs, validate requests, connect to a
database, call external services and run your business logic in the same server
that serves your pages. Forma supplies the UI and rendering contracts; your
application chooses its storage, authentication and service libraries.

This guide extends the published **3.0.0 minimal starter** from the
[installation guide](/docs/3.0/installation/).

## Understand the parts

| Part | Responsibility | Familiar JavaScript comparison |
| --- | --- | --- |
| Rust | Language used to write and compile the backend | JavaScript or TypeScript, rather than Node.js itself |
| Tokio | Async runtime used by the server | Some of the async execution work provided by Node.js |
| Axum | HTTP routes, request extraction and responses | Express or Fastify |
| Tower middleware | Processing around route handlers | Express middleware |
| `forma-server` | Typed page rendering and frontend asset integration | The rendering integration within your server |
| Browser `fetch` | Calls from your Forma frontend to an HTTP endpoint | The same browser Fetch API you already use |
| An HTTP client such as `reqwest` | Outgoing requests from Rust to another API | Server-side `fetch` or Axios |

These comparisons describe responsibilities, not identical APIs. **Axum receives
HTTP requests; an HTTP client sends them.** Rust compiles to a native executable;
it is not a JavaScript runtime. Node.js runs Forma's frontend build tools.

The minimal starter already declares Axum and Tokio in `Cargo.toml`. Its first
Cargo build downloads them along with the Forma crates. You do not need to
install Axum separately after creating the app, or run another web server beside
it. Rust and Cargo themselves remain prerequisites.

## Add a JSON endpoint

Open the starter's `src/main.rs`. Add this function outside `main`:

```rust
async fn api_items() -> axum::Json<Vec<&'static str>> {
    axum::Json(vec!["Field notebook", "Canvas pouch"])
}
```

In the existing `Router::new()` chain, add this line before `.with_state(state)`:

```rust
.route("/api/items", get(api_items))
```

Keep the existing page and asset routes, page binding and `finalize()` calls.
`get` is already imported by the starter. This JSON array uses the starter's
existing dependencies, so there is no manifest change. API routes do not need a
Forma page entry or an FMIR module in `admin/build.ts`.

After following the installation guide's frontend build, restart the server from
the application root:

```sh
cargo run --locked --jobs 3
```

In another terminal:

```sh
curl --fail http://localhost:3000/api/items
```

The response is `200 OK`, has a JSON content type and contains:

```json
["Field notebook", "Canvas pouch"]
```

This first endpoint returns fixed example data. It does not read a database or
save edits. From a browser event handler or other browser-owned code, call it
with ordinary Fetch:

```js
const response = await fetch("/api/items");
if (!response.ok) throw new Error(`Items request failed: ${response.status}`);
const items = await response.json();
```

Use the returned data to update your Forma signals. A native page handler can
instead call the same Rust service directly and pass the result into declared
page props; it does not need to make an HTTP request to its own API.

## Organize request handling and business logic

As the application grows, separate routes, services and storage into Rust
modules. A typical write follows this path:

```text
Browser form or fetch
  → Axum route and middleware
  → parse and validate the request
  → service applies business rules
  → storage commits the change
  → JSON response or redirect to fresh HTML
```

Axum extractors handle path parameters, query strings, forms, JSON and shared
application state. For your own typed JSON bodies and responses, a common next
dependency is Serde. Add it from the directory containing `Cargo.toml`:

```sh
cargo add serde@1 --features derive
```

Review and commit the resulting manifest and lockfile changes. Derive
`Deserialize` on request types and `Serialize` on response types. Parsing a
quantity as a number does not establish that the quantity is allowed: enforce
business rules and authorization in the backend before writing data. See
[Axum's extractors](https://docs.rs/axum/0.8.9/axum/extract/) and
[response types](https://docs.rs/axum/0.8.9/axum/response/).

Forma 3.0 uses explicit HTTP routes for browser-to-server operations. It does
not generate an RPC endpoint from a TypeScript `"use server"` function.
Ordinary Rust functions and service modules remain available throughout your
backend. You write the HTTP request and response contract explicitly, which adds
plumbing without restricting the backend operations you can implement. Explicit
routes still need application authentication, authorization and validation.

## Connect storage and distinguish schemas

Choose a Rust database driver or library appropriate to your database. For
example, [SQLx](https://docs.rs/sqlx/latest/sqlx/) provides database access,
connection pools and migrations. This is an application dependency, not a
bundled Forma database adapter. Follow that library's version, runtime and TLS
setup instructions when adding it to `Cargo.toml`.

Create the connection pool during server startup and keep it in your existing
`AppState`. Handlers access that state with Axum's `State` extractor. Reuse the
pool across requests; obtain connections or transactions for each operation.
Keep credentials in server configuration, outside browser bundles and page props.
See [sharing state in Axum](https://docs.rs/axum/0.8.9/axum/extract/struct.State.html).

Three contracts serve different purposes:

| Contract | What it describes |
| --- | --- |
| Database schema and migrations | Tables, constraints, indexes and changes to stored data |
| Request and response types | The API's input/output shape, with application validation |
| Forma `PageProps` | The declared data supplied to a rendered page and its islands |

`PageProps` does not create database tables or authorize a request. Services can
query, update and transact on stored data, then map it into the presentation
values the page needs. Backend Rust logic is not restricted to Forma's native
TSX expression subset.

## Add middleware and outgoing API calls

Axum uses Tower for middleware. Apply layers to the relevant router or group of
routes for authentication, request tracing, body limits or other request policy.
Place authorization before the protected operation; a CORS policy does not
establish a user's permission. Add any optional middleware crates deliberately
and follow [Axum's middleware guide](https://docs.rs/axum/0.8.9/axum/middleware/).
Keep Forma's complete `PageResponse` and its response headers when returning
rendered pages.

For calls from Rust to a payment, email or other external API, add a client such
as [reqwest](https://docs.rs/reqwest/latest/reqwest/). Reuse its client in shared
state, configure timeouts, handle unsuccessful responses and keep API keys on
the server. This is separate from Axum routing and from browser `fetch`.
Select dependency features and versions compatible with your application's
Rust toolchain, and commit the updated Cargo lockfile.

## Follow a complete saved edit

[Field Notes](/learn/full-stack/) is the runnable next step. Its
[source and download](/examples/field-notes/) show Axum routes, typed form input,
request checks, conflicts, a service/store layer and a browser Fetch enhancement
over ordinary HTML forms. The example verifies that accepted edits survive a
server restart.

Field Notes uses a single-process JSON file store and has no user accounts. It
demonstrates the application structure; it is not a database or authentication
integration. Use its tested write path as a starting point, then consult the
libraries you choose for their complete APIs. Continue with
[application testing](/docs/3.0/application-testing/) and
[deployment](/docs/3.0/deployment/) when those integrations are in place.
