Skip to main content

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), see Codegen plugin guide — don't read it to use the plugin, only to understand/extend it.

The whole story, in order

  1. 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.
  2. Author your model → run codegen → consume the generated code in a Hono app: building-a-server.md. Covers the Gradle build caveats (the java plugin requirement, the output-sync step, outputDirectory not being honored), choosing zero-handler @persisted CRUD vs hand-written handlers, DataStore selection per platform, errors, pagination, SSE, and MCP exposure.
  3. Secure it: security.md. The createSecurityPipeline assembly, PipelineConfig, OIDC cookie sessions, service-to-service HMAC, CSRF/CORS/headers, rate limiting, resource-level authorization, fail-fast config validation, and the production checklist.
  4. Deploy it: deployment.md. Platform matrix (Node/k8s, Cloudflare Workers, AWS Lambda) linking the in-repo deploy/* references, with the store wiring and secrets each needs.

Reference apps (read these — they are the gold implementations)

AppShows
examples/crud-apizero-handler @persisted CRUD over a DataStore, live MCP mount + stdio transport. No security pipeline.
examples/todo-apihand-written operation handlers + the full security pipeline, in memory and Redis variants; MCP as an OAuth resource server.
examples/secure-apiOIDC 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).

ArtifactKindCoordinate
@smithy-hono/security-corenpmGitLab npm registry
@smithy-hono/data-corenpmGitLab npm registry
@smithy-hono/adapter-nodenpmGitLab npm registry
@smithy-hono/adapter-cfnpmGitLab npm registry
@smithy-hono/adapter-awsnpmGitLab npm registry
@smithy-hono/adapter-postgresnpmGitLab npm registry
@smithy-hono/mcp-corenpmGitLab npm registry
@smithy-hono/test-kitnpmGitLab npm registry
Hono codegen pluginMaven (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 (or read_package_registry). Easiest for local development.
  • Deploy TokenProject/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:

PlatformInstall
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. MemoryDataStore is 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). See building-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.smithy into your own model sources. The plugin jar ships the traits only as Java TraitService providers — it does not bundle the trait shape definitions as a loadable Smithy model resource (there is no META-INF/smithy in the jar). Providers alone are not enough: a use com.smithyhono#persisted / @persisted in your IDL fails model assembly with "Use statement refers to undefined shape" / "Unable to resolve trait". Copy model/traits.smithy from this repo into your model package's model/ directory (next to your .smithy files) so the shapes resolve. Verified empirically: without the file smithy build fails; with it codegen succeeds. See building-a-server.md.

Two things the old example got wrong, now fixed in build.gradle.kts.example: the Smithy smithy-base plugin only creates the smithyBuild configuration when the java plugin is applied, and mavenLocal() should be listed first for a local-dev fallback. See building-a-server.md for 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"
}
}
}

outputDirectory is not honored by the Smithy Gradle plugin. Generated code always lands in build/smithyprojections/<root>/source/hono-codegen/ regardless of this value. You need a copy/sync step to move it into your server's src/generated. The working Sync/copy task is in building-a-server.md.


Troubleshooting

  • use com.smithyhono#persisted fails to resolve ("undefined shape" / "Unable to resolve trait") — most commonly you haven't copied model/traits.smithy into 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 the java plugin must be applied (otherwise the smithyBuild configuration doesn't exist and the dependency is silently dropped) and the dependency added with add("smithyBuild", "com.smithyhono:smithy-hono:0.1.1").
  • Generated code doesn't appear in src/generatedoutputDirectory is not honored by Gradle; you need the copy/sync task. See building-a-server.md.
  • 401 Unauthorized — token missing/expired, or GITLAB_TOKEN not exported in the shell running npm/gradle. For npm, confirm the _authToken line host matches the registry host 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/password credentials {} block.
  • CI can't read the registry — a CI_JOB_TOKEN from a different project must be allowlisted under this project's Settings → CI/CD → Job token permissions.