Building a production API server with smithy-hono
This is the consumer guide for smithy-hono: a Smithy build plugin (Maven jar)
that generates a Hono HTTP server from your Smithy model, plus a set of
@smithy-hono/* npm runtime packages (security pipeline, data stores, MCP
bridge, test kit) that you assemble into a real, secured, deployable server.
For the maintainer/publishing side, see
Publishing. For plugin internals (authoring traits, emitter design), seeCodegen plugin guide— don't read it to use the plugin, only to understand/extend it.
The whole story, in order
- Install & wire the artifacts (this file): auth tokens, npm packages, the
codegen plugin jar. Templates live beside this file:
.npmrc.example·build.gradle.kts.example·smithy-build.json.example·maven-settings.xml.example. - Author your model → run codegen → consume the generated code in a Hono
app:
building-a-server.md. Covers the Gradle build caveats (thejavaplugin requirement, the output-sync step,outputDirectorynot being honored), choosing zero-handler@persistedCRUD vs hand-written handlers, DataStore selection per platform, errors, pagination, SSE, and MCP exposure. - Secure it:
security.md. ThecreateSecurityPipelineassembly,PipelineConfig, OIDC cookie sessions, service-to-service HMAC, CSRF/CORS/headers, rate limiting, resource-level authorization, fail-fast config validation, and the production checklist. - Deploy it:
deployment.md. Platform matrix (Node/k8s, Cloudflare Workers, AWS Lambda) linking the in-repodeploy/*references, with the store wiring and secrets each needs.
Reference apps (read these — they are the gold implementations)
| App | Shows |
|---|---|
examples/crud-api | zero-handler @persisted CRUD over a DataStore, live MCP mount + stdio transport. No security pipeline. |
examples/todo-api | hand-written operation handlers + the full security pipeline, in memory and Redis variants; MCP as an OAuth resource server. |
examples/secure-api | OIDC cookie sessions, S2S HMAC import, owner-scoped resource authorization, fail-fast config validation. |
What ships
All artifacts release in lockstep at the same version (currently 0.1.1 —
verified against build.gradle.kts and every packages/*/package.json).
| Artifact | Kind | Coordinate |
|---|---|---|
@smithy-hono/security-core | npm | GitLab npm registry |
@smithy-hono/data-core | npm | GitLab npm registry |
@smithy-hono/adapter-node | npm | GitLab npm registry |
@smithy-hono/adapter-cf | npm | GitLab npm registry |
@smithy-hono/adapter-aws | npm | GitLab npm registry |
@smithy-hono/adapter-postgres | npm | GitLab npm registry |
@smithy-hono/mcp-core | npm | GitLab npm registry |
@smithy-hono/test-kit | npm | GitLab npm registry |
| Hono codegen plugin | Maven (jar) | com.smithyhono:smithy-hono:0.1.1 |
The adapters declare @smithy-hono/security-core and @smithy-hono/data-core
as peer dependencies (^0.1.1), so a consumer installs each core once and
the adapters bind to it. @smithy-hono/test-kit is a devDependency;
@smithy-hono/key-tool is intentionally not published (it is a dev-only
CLI).
0. Get an access token (one-time)
The registry is private, so every consumer authenticates. Pick one:
- Personal Access Token — scope
read_api(orread_package_registry). Easiest for local development. - Deploy Token — Project/Group → Settings → Repository → Deploy tokens,
scope
read_package_registry. Best for shared/CI use; not tied to a person. CI_JOB_TOKEN— automatic inside GitLab CI, no setup. If the consuming project differs from this one, grant it access under this project's Settings → CI/CD → Job token permissions.
Never commit the token. Export it as an environment variable and let the config files below interpolate it.
Project ID
The Maven endpoints (and the npm project-level fallback) need the numeric
Project ID, which is 11 for smithy-hono/smithy-hono.
Verify it on the project overview page (⋮ menu → Copy project ID) or Settings → General — if this instance has been re-provisioned the ID may differ.
1. npm packages (@smithy-hono/*)
Add .npmrc.example to your project as .npmrc. Because the
npm scope (@smithy-hono) matches the top-level GitLab group (smithy-hono),
the instance-level endpoint works and needs no Project ID:
@smithy-hono:registry=https://gitlab.example.com/api/v4/packages/npm/
//gitlab.example.com/api/v4/packages/npm/:_authToken=${GITLAB_TOKEN}
Then:
export GITLAB_TOKEN=<your token>
npm install @smithy-hono/security-core @smithy-hono/data-core @smithy-hono/adapter-node
Install the core(s) plus the adapter for your platform:
| Platform | Install |
|---|---|
| Node + Redis | @smithy-hono/adapter-node |
| Cloudflare Workers (D1 / KV / Durable Objects) | @smithy-hono/adapter-cf |
| AWS (DynamoDB + Secrets Manager) | @smithy-hono/adapter-aws |
| Postgres (durable data store) | @smithy-hono/adapter-postgres |
| MCP server bridge | @smithy-hono/mcp-core |
| Consumer test toolkit (dev) | @smithy-hono/test-kit |
hono and (per adapter) ioredis / the AWS SDK / Cloudflare types / pg are
peer dependencies — install them in the consumer. Check each package's
peerDependencies for exact client versions.
Subpath exports to know about.
MemoryDataStoreis at@smithy-hono/data-core/memory(the main barrel exports only types + the error class). The MCP stdio transport is at@smithy-hono/mcp-core/stdio(Node-only). Seebuilding-a-server.md.
Project-level fallback (use if the instance-level endpoint is rejected — e.g. your group name does not match the scope):
@smithy-hono:registry=https://gitlab.example.com/api/v4/projects/11/packages/npm/
//gitlab.example.com/api/v4/projects/11/packages/npm/:_authToken=${GITLAB_TOKEN}
2. The Hono codegen plugin (Maven jar)
com.smithyhono:smithy-hono is a Smithy SmithyBuildPlugin registered as
hono-codegen. Put the jar on the Smithy build classpath, then name the
plugin in smithy-build.json.
⚠️ You must copy
model/traits.smithyinto your own model sources. The plugin jar ships the traits only as JavaTraitServiceproviders — it does not bundle the trait shape definitions as a loadable Smithy model resource (there is noMETA-INF/smithyin the jar). Providers alone are not enough: ause com.smithyhono#persisted/@persistedin your IDL fails model assembly with "Use statement refers to undefined shape" / "Unable to resolve trait". Copymodel/traits.smithyfrom this repo into your model package'smodel/directory (next to your.smithyfiles) so the shapes resolve. Verified empirically: without the filesmithy buildfails; with it codegen succeeds. Seebuilding-a-server.md.
Gradle (recommended)
Two things the old example got wrong, now fixed in
build.gradle.kts.example: the Smithysmithy-baseplugin only creates thesmithyBuildconfiguration when thejavaplugin is applied, andmavenLocal()should be listed first for a local-dev fallback. Seebuilding-a-server.mdfor the complete, working build file including the output-sync task.
The essentials:
plugins {
java // REQUIRED: creates the smithyBuild configuration
id("software.amazon.smithy.gradle.smithy-base") version "1.2.0"
}
repositories {
mavenLocal() // local-dev fallback first
mavenCentral()
maven {
name = "GitLabSmithyHono"
url = uri("https://gitlab.example.com/api/v4/projects/11/packages/maven")
credentials(HttpHeaderCredentials::class) {
name = "Private-Token" // or "Deploy-Token", or "Job-Token" in CI
value = System.getenv("GITLAB_TOKEN")
}
authentication { create<HttpHeaderAuthentication>("header") }
}
}
dependencies {
add("smithyBuild", "com.smithyhono:smithy-hono:0.1.1") // discoverable via ServiceLoader
}
Smithy CLI / plain smithy-build.json
Declare the GitLab Maven repo and dependency directly — see
smithy-build.json.example:
{
"version": "1.0",
"maven": {
"repositories": [
{ "url": "https://gitlab.example.com/api/v4/projects/11/packages/maven" }
],
"dependencies": ["com.smithyhono:smithy-hono:0.1.1"]
}
}
Plain Maven
Add the project Maven URL as a <repository> in pom.xml, and put the token in
~/.m2/settings.xml as a matching <server> with a Private-Token HTTP header
— see maven-settings.xml.example.
Reference the plugin
The plugin runs when named under plugins:
{
"version": "1.0",
"sources": ["model"],
"plugins": {
"hono-codegen": {
"service": "com.example#MyService",
"outputDirectory": "generated",
"packageName": "my-service-generated",
"packageVersion": "0.1.0"
}
}
}
outputDirectoryis not honored by the Smithy Gradle plugin. Generated code always lands inbuild/smithyprojections/<root>/source/hono-codegen/regardless of this value. You need a copy/sync step to move it into your server'ssrc/generated. The workingSync/copytask is inbuilding-a-server.md.
Troubleshooting
use com.smithyhono#persistedfails to resolve ("undefined shape" / "Unable to resolve trait") — most commonly you haven't copiedmodel/traits.smithyinto your model sources (the jar does not ship loadable trait definitions — see §2). Also confirm the plugin jar is actually on the Smithy build path: with Gradle thejavaplugin must be applied (otherwise thesmithyBuildconfiguration doesn't exist and the dependency is silently dropped) and the dependency added withadd("smithyBuild", "com.smithyhono:smithy-hono:0.1.1").- Generated code doesn't appear in
src/generated—outputDirectoryis not honored by Gradle; you need the copy/sync task. Seebuilding-a-server.md. - 401 Unauthorized — token missing/expired, or
GITLAB_TOKENnot exported in the shell runningnpm/gradle. For npm, confirm the_authTokenline host matches theregistryhost exactly. - 404 on an npm package — the instance-level endpoint requires the scope to
match the top-level group. If your setup differs, switch to the project-level
URL (
/projects/11/...). - Gradle ignores the credentials — the GitLab Maven registry needs header
auth (
HttpHeaderCredentials+HttpHeaderAuthentication), not the default username/passwordcredentials {}block. - CI can't read the registry — a
CI_JOB_TOKENfrom a different project must be allowlisted under this project's Settings → CI/CD → Job token permissions.