Integration Guide
Express.js integration guide
StateAnchor acts as a CI gate between your Express API and the SDK consumers that depend on it. Every push that touches your stateanchor.yaml triggers a diff against the last known contract snapshot. If the change is breaking, the push is blocked before any consumer is affected.
This guide is for solo developers and teams running Express 4.x or 5.x services with typed consumers -- whether internal microservices, mobile clients, or external partners calling a documented REST API.
What you’ll need
- Node.js 18 or later
- Express 4.x or 5.x
- A running Express API with at least one route
- A GitHub repository (public or private)
- A StateAnchor account -- connect a repo to start
Step 1 -- Map your API to stateanchor.yaml
Create a stateanchor.yaml file in the root of your repository. The file declares your service name, server URL, data models, endpoints, and the SDK outputs you want generated. StateAnchor derives the contract from this file -- it does not instrument your Express routes directly.
Here is a complete example for a Users REST API:
service: users-api
version: "1.0.0"
info:
title: Users API
description: User management REST API for the Acme platform
server:
base_url: https://api.acme.com
auth:
type: bearer
models:
User:
id: uuid
name: string
email: string
role: string
created_at: string
UserCreate:
name: string
email: string
role: string
endpoints:
- name: listUsers
method: GET
path: /users
description: List all users (paginated)
- name: getUser
method: GET
path: /users/{id}
description: Get a single user by ID
returns:
$ref: User
- name: createUser
method: POST
path: /users
description: Create a new user
body:
$ref: UserCreate
returns:
$ref: User
- name: updateUser
method: PUT
path: /users/{id}
description: Update user fields
body:
$ref: UserCreate
returns:
$ref: User
- name: deleteUser
method: DELETE
path: /users/{id}
description: Permanently delete a user
outputs:
typescript: true
python: trueA few things to note about this spec:
- service -- a stable identifier for your API. Do not change this once consumers have taken a dependency on it.
- version -- follows semver. Bump the minor version when you add new endpoints; bump major when you make a breaking change that you’ve approved via an exception.
- models -- define reusable data shapes. Use PascalCase names matching your TypeScript interfaces or JSDoc typedefs.
- endpoints -- one entry per route. The
pathfield uses Express-style path parameters ({id}). - outputs -- at least one must be
true. StateAnchor generates TypeScript and Python typed wrappers on every passing sync.
Step 2 -- Add the GitHub Action
The StateAnchor GitHub Action runs on every push and pull request. It validates your spec, diffs it against the last approved snapshot, and posts a gate verdict to the PR as a required status check. Breaking changes block the merge.
Create .github/workflows/stateanchor.yml:
name: StateAnchor gate
on:
push:
branches: ["main", "master"]
pull_request:
permissions:
contents: read
statuses: write
pull-requests: write
jobs:
gate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: StateAnchor sync
uses: stateanchor/sync-action@v1
with:
api-key: ${{ secrets.STATEANCHOR_API_KEY }}Add STATEANCHOR_API_KEY to your repository secrets (Settings → Secrets and variables → Actions). You can find your API key in the StateAnchor dashboard under project settings.
Step 3 -- Understand the gate result
After each push, StateAnchor posts a verdict comment to the PR and sets a commit status. There are three categorical lanes:
- PASS -- no breaking changes. SDK generation runs. The merge is unblocked.
- WARN -- degradation detected (e.g., an optional field removed). The merge is not blocked by default, but the verdict is logged.
- ERR -- breaking change detected. The merge is blocked until the change is reverted, fixed, or an exception is approved.
Common ERR kinds that show up in Express APIs:
endpoint_removed-- a route was deleted or renamed. Consumers that call it will receive 404.required_param_added-- a new required query parameter was added to a GET endpoint. Existing callers will start receiving 400.type_changed-- a field’s type was changed (e.g.,id: uuidchanged toid: integer). Generated SDK types will diverge.
See Gate Classification for the complete list of ERR, WARN, and INFO change kinds.
Step 4 -- Handling a block
When the gate blocks a push, you have three options: fix the change so the contract is preserved, approve an intentional breaking change via a drift exception, or investigate whether the classification is wrong. The full remediation flow is documented in Handling a block.
Common patterns for Express
Versioned routes (/v1, /v2)
Express teams often version routes by URL prefix. Treat each major version as a separate service in your stateanchor.yaml:
# stateanchor.yaml for v1
service: users-api-v1
version: "1.0.0"
server:
base_url: https://api.acme.com
auth:
type: bearer
endpoints:
- name: listUsers
method: GET
path: /v1/users
outputs:
typescript: trueWhen you are ready to release v2 with breaking changes, create a newstateanchor-v2.yaml (and point the Action at it with config-path) rather than modifying the v1 spec. This preserves the v1 contract for consumers who have not migrated.
Middleware-applied auth
Express typically applies auth via middleware to groups of routes. Map this to StateAnchor by setting server.auth.type for the service default, then override individual endpoints that are public:
server:
base_url: https://api.acme.com
auth:
type: bearer # default: all routes require bearer auth
endpoints:
- name: healthCheck
method: GET
path: /health
auth: none # override: this route is publicExpress Router modules
Express Router lets you split routes across files (userRouter,paymentRouter, etc.). StateAnchor operates at the service level, not the router level. Use one stateanchor.yaml per service, listing all routes regardless of which router file they live in. If you have multiple completely separate services (separate processes, separate base URLs), use one spec file per service.
Next steps
- Drift exceptions -- approve intentional breaking changes with the two-signal model
- Gate engine -- how the four drift syndromes drive the gate decision
- stateanchor.yaml reference -- complete field-by-field spec documentation