v0.8.0

Released: October 2, 2026

This release is about the gateway. API keys are now real runtime data: you create, limit, rotate and revoke them through the auth-service, and the gateway enforces them on every request with no redeploy. Along the way we layered the trust between the gateway and your services, gave users roles, and made the gateway image the same everywhere. It has breaking changes; the upgrade guide at the end walks through them in order.


API keys you manage at runtime

Partner keys used to be static: a secret in .secrets.user.json, a consumer in kong.user.yml, and a gateway rebuild for every new partner or rotation. That is gone. Keys now live in Postgres, with a copy in Redis that the gateway reads.

  • Admins create keys for a named consumer (acme-corp), not a user, through new admin endpoints on the auth-service. The key is shown once and stored only as a hash.
  • Each key can have its own limits per minute, hour, day, week and month, and an optional expiry. A trial is just a key with low limits and a near expiry.
  • Rotation issues a new key and keeps the old one working for a grace period (7 days by default). Revocation takes effect on the next request.
  • The global rate limit in kong.user.yml becomes the default for every key, counted per key. A key's own limits replace it, higher or lower.
  • A per-IP ceiling on API key routes (600 requests per minute by default, framework.apiKeys.ipLimitPerMinute in .tsdevstack/config.json) slows down anyone guessing keys.
  • Clear error codes from the gateway: api_key_missing, invalid_api_key, api_key_revoked, api_key_expired, rate_limit_exceeded, quota_exceeded, and 503 index_unavailable or gateway_unavailable when keys cannot be checked. Validation fails closed.
  • A scheduled job, sync-api-key-usage, saves week and month usage to Postgres and rebuilds the Redis copy if Redis loses its data.
  • Projects without the auth template can write keys to Redis with their own tooling. The record format is a documented, versioned contract, and @tsdevstack/nest-common exports it as code.

Everything is in the new API Keys page.

Exact routes at the gateway

The gateway now routes only what your OpenAPI document declares: the exact path and its declared methods, plus OPTIONS for CORS preflights. Everything else gets 404 from the gateway before it reaches a service: unknown paths, extra path segments, a trailing slash, undeclared methods (including HEAD on a GET route), and endpoints hidden from OpenAPI.

Partner routes use the same model: each @PartnerApi() operation gets its own route at /api plus its path. See Gateway Routing.

Token checks got faster too: the gateway caches the auth-service's OIDC discovery document and signing keys for 24 hours instead of fetching them per request. New signing keys are picked up right away.

Gateway and service trust, layered

  • A new built-in gateway plugin, tsdevstack-strip-identity, clears identity headers (X-Userinfo, X-Consumer-*, X-Api-Key-*, X-Kong-Trust and similar) on every incoming request, before authentication runs. Only the gateway itself sets them.
  • AuthGuard checks the gateway's trust token first, then the identity headers. Without a valid token, identity headers are ignored: the request is an internal service call with the service API_KEY, an anonymous call to a @Public() handler, or 401.
  • A partner key only works on @PartnerApi() handlers, and gets 403 anywhere else, @Public() handlers included.

Details in Protected Routes.

Roles and managing users

  • Every user has a system role (USER or ADMIN) and any number of custom roles you declare. Restrict any endpoint with @Roles() from nest-common.
  • The first admin comes from a new ADMIN_EMAILS secret: a confirmed user listed there becomes admin at login. generate-secrets adds it to your .secrets.user.json, and cloud-secrets:push offers it.
  • Admin endpoints to find users and change their roles, and a change-password endpoint for logged-in users.

See Roles and Managing Users.

One gateway image, locally and in the cloud

The gateway image is now generated from one definition for local development and every cloud provider. infrastructure/kong/Dockerfile and .dockerignore are generated files. Your own Lua plugins go in a kong-plugins/ folder at the project root and end up in both the local and the cloud image. See Kong Plugins.

AWS: scale-to-zero retired

We retired scale-to-zero on AWS to keep the setup simple. Waking a sleeping service on ECS took extra moving parts and was not reliable enough. Services and the gateway now always run at least one task and scale on CPU between minInstances and maxInstances.

This costs more: every AWS service keeps at least one task running. The AWS cost estimates are updated. GCP and Azure keep minInstances: 0.

init uses templates that match your CLI

init now clones the templates tagged with your CLI's version, so a new project always gets templates that work with the packages it installs. If the tag does not exist (an unreleased build), it falls back to the latest templates with a warning.

Smaller fixes

  • Rate limiting and health checks recover after a Redis interruption. The shared Redis client used to give up after a few retries and needed a service restart.
  • Regenerated CI workflows print the current gateway commands.
  • Environment password protection was removed a while ago, but its config fields were still accepted and silently ignored. They are now rejected.
  • AWS deployment fixes: scheduled job authentication, Next.js assets through CloudFront, migration status reporting.
  • Single-service deploys failed with a false "topology changed" error in projects with a worker.
  • Commands no longer fail with "Backend configuration changed" after an environment moves to a new project or provider.
  • Azure: cloud-secrets:list shows service-scoped secrets.
  • SecretsService.getProvider().getAll() returns only shared secrets and the service's own.
  • Clearer hints in generate-kong and for missing images.
  • New on Azure: database.serverName sets the PostgreSQL server name, for when the default name is taken. See Custom server name.

Breaking changes

Pre-1.0, so they ship in a minor version. Each one is covered by the upgrade guide below.

API keys

  • Static partner keys no longer work. Consumers with keyauth_credentials in kong.user.yml stay in the merged config, but no generated route checks them; generate-kong and infra:generate-kong warn while they are there. They stop working when you regenerate and redeploy the gateway.
  • Partner keys are not secrets anymore. Old partner key values in .secrets.user.json and your cloud secret manager can go.
  • With the auth template, every cloud environment needs the sync-api-key-usage scheduled job. infra:generate warns and prints the entry until it is there.
  • The auth-service needs Redis 7.0 or later. Every generated environment and the local compose file already run 7.x.
  • An admin who created API keys cannot be deleted from the database while those keys exist.

Gateway

  • Undeclared paths, methods, trailing slashes and extra segments get 404 from the gateway. HEAD is not added to GET routes.
  • Endpoints missing from OpenAPI (@ApiExcludeEndpoint(), @ApiExcludeController(), raw Express routes, static files) are unreachable through the gateway. That is intended for jobs, health checks and metrics; anything else must be in the OpenAPI document.
  • NestJS wildcard (*splat) and optional ({/:id}) routes appear in OpenAPI as a single segment or one variant, so the gateway now routes only that. Declare the variants you need as separate routes.
  • Partner routes exist only for @PartnerApi() operations. The old catch-all /api/{prefix} route is gone.
  • Gateway route names changed ({service}-{public|jwt|partner}-{path}). Anything that referred to the old names breaks.
  • infrastructure/kong/Dockerfile and .dockerignore are generated and overwritten by generate-kong. Custom plugins move to kong-plugins/.
  • A fresh clone needs npx tsdevstack sync (or generate-kong) before docker compose up, because parts of the gateway build context are generated. docker compose up without --build keeps the old gateway image, which cannot start with the new config.
  • The request-transformer in kong.user.yml must only handle X-Kong-Trust and X-Kong-Request-Id. Older files also remove X-Consumer-* and X-JWT-Claim-* headers; the generators warn until you delete those entries.
  • A full kong.custom.yml must now declare the framework plugins itself: the global tsdevstack-strip-identity, and tsdevstack-api-key, a per-IP rate-limiting and tsdevstack-api-prefix on partner services.
  • Services without a globalPrefix now use the same prefix rule locally as in the cloud (the service name without -service).
  • Locally, the gateway reads the client IP from X-Forwarded-For, the same as behind a cloud load balancer.
  • The gateway image sets KONG_NGINX_HTTP_INCLUDE for its OIDC caches. If you set it on the gateway container yourself, you replace the framework's include and the caches stop working. Other KONG_NGINX_HTTP_* settings are unaffected.
  • The gateway keeps the auth-service's signing keys for up to 24 hours. If you ever remove a signing key in an emergency, redeploy the gateway (infra:deploy-kong) so it stops accepting tokens signed with it.

Backends (nest-common)

  • Requests that did not come through the gateway get 401 on protected handlers when they carry identity headers. That includes calling localhost:300x directly with a bearer token, Swagger UI's "Try it out" on protected endpoints, end-to-end tests against service ports and sidecars. Go through the gateway, or use the service API_KEY for internal calls.
  • Partner requests never set req.user. They set req.authType === 'apiKey', req.apiKey = { id, consumer } and req.service === 'partner'; @Partner() returns the consumer name. Internal calls that forward a partner's headers count as partner calls downstream, so the downstream handler needs @PartnerApi() too.
  • A wrong service x-api-key on a @Public() handler is ignored instead of rejected. A trusted request with an x-api-key but no gateway identity is checked against the service API_KEY.
  • KongHeaders.JWT_CLAIM_PREFIX, KongHeaders.CONSUMER_ID, KongHeaders.CONSUMER_USERNAME and KongHeaders.CREDENTIAL_IDENTIFIER are removed, and X-JWT-Claim-* headers are no longer read. Read claims from req.user.
  • RateLimitGuard uses new Redis key names, so rate-limit counters reset on deploy. Per-IP limits use the gateway's X-Real-IP only for requests that came through the gateway, and never X-Forwarded-For.
  • With the Redis health check enabled (HealthModule.forRoot({ redis: true })), /health reports Redis down during an outage (overall status degraded, still HTTP 200), so monitoring may alert where it stayed quiet before.

Auth template

  • The user's role field is now systemRole (enum UserType is now SystemRole), next to a new roles list. Tokens carry systemRole and roles instead of role, and UserDto changed the same way, so a regenerated client breaks frontend code that reads user.role. @Roles() still accepts older tokens with a role claim.
  • A plain prisma migrate dev after copying the new schema drops the role column and resets everyone to USER. Use the rename migration from the upgrade guide instead.
  • PUT /v1/user/password revokes every refresh token of the user and returns a new token pair; a frontend must store it or its session ends at the next refresh.

AWS

  • minInstances: 0 is rejected for services and the gateway on AWS. Set 1 or more.
  • The next infra:deploy removes the wake Lambda, the idle alarms and scale-to-zero policies, and the load balancer's internal listeners. The gateway reaches services over Cloud Map. Until the gateway is rebuilt and redeployed, the old gateway returns errors for every backend route; a full infra:deploy does both.
  • Custom Lua in kong.user.yml that relied on the wake-up environment variables (WAKEUP_LAMBDA_URL and the Lua sandbox settings) breaks. The ALB_INTERNAL_DNS secret is no longer used.
  • Every AWS service keeps at least one task running, so running cost goes up.

Config

  • accessControl.protected and accessControl.cookieTtlHours in infrastructure.json fail validation. Remove them.

For CLI plugin authors

  • generateKeyAuthPlugin and KeyAuthPluginConfig are removed. getDefaultKongPlugins() takes no argument, generateKongDockerfile and writeKongBuildContext no longer take a provider, and openApiPathToKongRegex is no longer in the plugin context. ServiceRouteConfig requires partnerApi.

nest-common 0.8.0

@tsdevstack/nest-common releases alongside the CLI. New: @Roles(), RolesGuard, ROLES_KEY, @ApiKey(), the AuthType and AuthenticatedApiKey types, RedisService.isReady() and onReady(), and the API key record contract (hashApiKey, buildApiKeyRecordKey, encodeApiKeyRecord and friends). The behavior changes are in the breaking changes above.

Upgrading an existing project

Do these in order. Steps 3 to 7 only apply to projects on the auth template; projects without it skip to the gateway part of step 6.

1. Update the packages and the gateway files.

npm install -D @tsdevstack/cli@^0.8.0
npm install @tsdevstack/nest-common@^0.8.0 -w auth-service   # repeat for each NestJS service
  • In kong.user.yml, remove every identity header from the request-transformer remove list. Keep only X-Kong-Request-Id and X-Kong-Trust.
  • If you added Lua plugins to infrastructure/kong/Dockerfile by hand, move each one into its own folder under kong-plugins/ and enable it in kong.user.yml. The Dockerfile is regenerated.
  • If a service has no globalPrefix and partners call its old local URLs, set globalPrefix explicitly in .tsdevstack/config.json.
  • If you use a full kong.custom.yml, add the framework plugins listed in Kong Customization.
  • Run npx tsdevstack sync. Then check anything that calls service ports directly, and endpoints that relied on prefix matching, trailing slashes or methods missing from OpenAPI.
  • Run npx tsdevstack infra:generate-ci if you use the generated CI workflows.

2. AWS only: drop scale-to-zero. Remove minInstances: 0 from every service and from kong in infrastructure.json (use 1 or more). Run infra:plan to see what goes away, then infra:deploy: it removes the wake machinery, redeploys the gateway, renames the scheduled-job secret so jobs authenticate, and fixes the Next.js assets behind CloudFront. Expect a higher monthly bill. Optionally remove the unused secret with npx tsdevstack cloud-secrets:remove ALB_INTERNAL_DNS --env <env>.

3. Bring the new auth-service code over. Your auth-service is a copy you own, so the framework cannot update it for you. Scaffold a fresh project with the new CLI (npx @tsdevstack/cli init) and copy from its apps/auth-service:

  • src/api-keys/, src/admin/, src/roles/, and the changes in src/jobs/, src/user/, src/auth/ and src/app.module.ts,
  • the User, ApiKey and ApiKeyUsage models and the SystemRole and ApiKeyStatus enums in prisma/schema.prisma.

Then create the migrations from apps/auth-service, in two steps. First the role rename, written by hand so nobody loses their role:

npx prisma migrate dev --create-only --name rename-role-to-system-role

Replace the generated migration.sql with:

ALTER TYPE "UserType" RENAME TO "SystemRole";
ALTER TABLE "User" RENAME COLUMN "role" TO "systemRole";
ALTER TABLE "User" ADD COLUMN "roles" TEXT[] DEFAULT ARRAY[]::TEXT[];

Apply it and create the API key tables:

npx prisma migrate dev --name add-api-keys

Regenerate the auth-service client (npx tsdevstack generate-client auth-service) and replace role with systemRole wherever your code or frontend reads it. Commit the migrations; cloud deploys apply them. See Database Migrations.

4. Become admin. Set ADMIN_EMAILS to your email in .secrets.user.json and run npx tsdevstack generate-secrets. In each cloud environment: npx tsdevstack cloud-secrets:set ADMIN_EMAILS --service auth-service --env <env>. Log in with a confirmed account and check that systemRole is ADMIN. Walkthrough: Managing Users.

5. Issue new keys to existing partners. Deploy the auth-service (infra:deploy-service auth-service --env <env>, or a full deploy). Create a key for each partner with POST /auth/v1/admin/api-keys and hand the keys over. The old static keys keep working until the next step, so partners can switch at their own pace.

6. Regenerate and redeploy the gateway. Locally: npx tsdevstack sync. In each cloud environment:

npx tsdevstack infra:generate-kong --env <env>   # commit infrastructure/kong/<env>/kong.yml
npx tsdevstack infra:build-kong --env <env>
npx tsdevstack infra:deploy-kong --env <env>

From here only the new keys work. To change the per-IP ceiling on API key routes, set framework.apiKeys.ipLimitPerMinute in .tsdevstack/config.json before this step.

7. Schedule the usage job and clean up. Add the sync-api-key-usage job to scheduledJobs of every environment; infra:generate prints the exact entry for your project, and the API Keys page shows it. Deploy it with npx tsdevstack infra:deploy-schedulers --env <env>. Then delete the partner consumers from kong.user.yml and the old key values from .secrets.user.json and each cloud environment (npx tsdevstack cloud-secrets:remove <KEY> --env <env>).

New projects get all of this from the templates.