Local Secrets

During local development, tsdevstack stores secrets in JSON files and provides them to services through a consistent interface. This page covers how to work with secrets in your local environment.

The Secrets Files

After running npx tsdevstack generate-secrets, you will have these files in your project root:

.secrets.tsdevstack.json  # Framework-generated secrets (do not edit)
.secrets.user.json        # Your custom secrets (edit this one)
.secrets.local.json       # Merged output (do not edit)
.env                      # Docker/Kong variables (generated from secrets)
apps/auth-service/.env    # Per-service env (DATABASE_URL for Prisma, etc.)
apps/frontend/.env        # Frontend env (API_URL, token TTLs, etc.)

All of these files are gitignored. The per-app .env files are generated by generate-secrets so that tools like Prisma CLI and Next.js can read environment variables directly. Backend services should still use SecretsService at runtime — the .env files exist for CLI tools (e.g., npx prisma studio) and frontend builds.

Adding Your Own Secrets

To add a custom secret, edit .secrets.user.json:

{
  "secrets": {
    "STRIPE_API_KEY": "sk_test_your_key_here",
    "SENDGRID_API_KEY": "SG.your_key_here"
  },
  "auth-service": {
    "secrets": ["STRIPE_API_KEY"]
  },
  "bff-service": {
    "secrets": ["SENDGRID_API_KEY"]
  }
}

Then regenerate:

npx tsdevstack generate-secrets

The secrets object at the top level defines the values. The service sections specify which services receive which secrets.

Overriding Framework Defaults

You can override any framework-generated value by adding it to your user file:

{
  "secrets": {
    "REDIS_HOST": "my-redis.example.com",
    "ACCESS_TOKEN_TTL": "1800"
  }
}

Your values always take precedence over framework defaults.

Accessing Secrets in Backend Services

Backend services (NestJS) use the SecretsService to access secrets. Never use process.env directly.

import { Injectable } from '@nestjs/common';
import { SecretsService } from '@tsdevstack/nest-common';

@Injectable()
export class PaymentService {
  constructor(private readonly secrets: SecretsService) {}

  async processPayment() {
    const stripeKey = await this.secrets.get('STRIPE_API_KEY');
    // Use the key...
  }
}

This pattern ensures your code works the same way locally and in production.

Accessing Secrets in Frontend (Next.js)

Frontend applications receive secrets through generated .env files. Access them via process.env:

const API_URL = process.env.API_URL;
const ACCESS_TOKEN_TTL = parseInt(process.env.ACCESS_TOKEN_TTL || '900', 10);

To add a secret to your frontend, update .secrets.user.json:

{
  "frontend": {
    "secrets": ["API_URL", "ACCESS_TOKEN_TTL"]
  }
}

Accessing Secrets in SPAs (Rsbuild)

SPAs receive secrets through generated .env files (created by generate-secrets based on your secrets configuration) and expose them via build configuration:

// In your SPA code
const API_URL = import.meta.env.API_URL;

Configure which env vars are bundled in your rsbuild.config.js:

import { defineConfig, loadEnv } from '@rsbuild/core';

const { publicVars } = loadEnv();

export default defineConfig({
  source: {
    define: {
      ...publicVars,
      'import.meta.env.API_URL': JSON.stringify(process.env.API_URL),
    },
  },
});

Security note: Any secret exposed to an SPA is visible in the browser. Only expose values that are safe to be public, like API URLs.

Common Workflows

Initial Setup

npx tsdevstack sync
npm run dev

The sync command generates secrets, Kong configuration, and other required files.

Adding a New Third-Party Service

  1. Add the API key to .secrets.user.json:
{
  "secrets": {
    "TWILIO_API_KEY": "your_key"
  },
  "auth-service": {
    "secrets": ["TWILIO_API_KEY"]
  }
}
  1. Regenerate and restart:
npx tsdevstack generate-secrets
npm run dev
  1. Access in your service:
const twilioKey = await this.secrets.get('TWILIO_API_KEY');

Troubleshooting

"SECRETS_PROVIDER not set"

Run npx tsdevstack generate-secrets to create the necessary files.

Secret Not Available in Service

  1. Check that the secret is defined in the secrets object in .secrets.user.json
  2. Check that the service lists the secret in its secrets array
  3. Regenerate with npx tsdevstack generate-secrets
  4. Restart the service

Changes Not Taking Effect

After modifying .secrets.user.json:

  1. Run npx tsdevstack generate-secrets
  2. Wait up to 1 minute for the cache to refresh (SecretsService has a 1-minute TTL)

Note: Backend services using SecretsService automatically reload secrets from the file when the cache expires. Restarting is only needed if you're using process.env directly (which you shouldn't in backend services).