Better-TS-Stack

Configuration

Environment variables, package.json, and TypeScript configuration

Configuration Overview

This page lists the configuration files a generated project contains and the environment variables you need to set.


Environment Variables

Backend (.env)

The CLI writes these variables to .env and .env.example, depending on the modules you selected:

# Base (always generated). NODE_ENV is set by the dev and start scripts.
PORT=3000

# + PostgreSQL (Prisma or Drizzle)
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/postgres?schema=public"

# + MongoDB (Mongoose)
MONGODB_URI="mongodb://localhost:27017/myapp"

# + JWT Auth (Express)
JWT_SECRET=please-change-me
JWT_EXPIRES_IN=1h

Full-stack / Next.js (.env)

# TanStack Start only. Next.js picks its port before it loads .env.
# NODE_ENV is set by next, vite, and the Nitro server.
PORT=3000

# + Better Auth (requires database)
BETTER_AUTH_SECRET=please-change-me-to-a-random-string
BETTER_AUTH_URL=http://localhost:3000

.env.example keeps the placeholder values shown here. In .env, the CLI replaces JWT_SECRET and BETTER_AUTH_SECRET with a random 64-character value, so each project gets its own secret.

Environment-Specific Files

You can create separate files for different environments:

  • .env.development - Development-specific
  • .env.production - Production-specific
  • .env.test - Testing environment

Never commit .env files to version control! Use .env.example as a template.


TypeScript Configuration

Backend tsconfig.json

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "types": ["node"],
    "lib": ["ES2020"],
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

Frontend tsconfig.json

The Next.js template ships with this tsconfig.json:

{
  "compilerOptions": {
    "target": "ES2017",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "bundler",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "react-jsx",
    "incremental": true,
    "plugins": [
      {
        "name": "next"
      }
    ],
    "paths": {
      "@/*": ["./*"]
    }
  },
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
  "exclude": ["node_modules"]
}

Package.json Scripts

Backend (Express) scripts

These are generated by the Express base module:

{
  "scripts": {
    "dev": "cross-env NODE_ENV=development tsx watch src/index.ts",
    "build": "tsc",
    "start": "cross-env NODE_ENV=production node dist/index.js",
    "lint": "eslint src",
    "lint:fix": "eslint src --fix",
    "format": "prettier --write \"src/**/*.ts\"",
    "type:check": "tsc --noEmit"
  }
}

With bun, dev runs bun --watch src/index.ts and start runs bun dist/index.js, still through cross-env.

Prisma module adds: prisma:generate, prisma:migrate, prisma:studio

Docker module adds: docker:build, docker:up, docker:down, docker:logs

Full-stack (Next.js) scripts

These are generated by the Next.js base module:

{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "eslint"
  }
}

Prisma module adds: prisma:generate, prisma:migrate, prisma:studio

Drizzle module adds: db:generate, db:migrate, db:studio

Docker module adds: docker:build, docker:up, docker:down, docker:logs


ESLint Configuration

Backend eslint.config.mjs

ESLint 10 reads the flat config in eslint.config.mjs. The Express template uses typescript-eslint with type-aware rules. A shortened version:

import eslint from "@eslint/js";
import tseslint from "typescript-eslint";

export default tseslint.config(
  { ignores: ["dist", "node_modules", "src/generated/**"] },
  eslint.configs.recommended,
  ...tseslint.configs.recommended,
  ...tseslint.configs.recommendedTypeChecked,
  {
    languageOptions: {
      parserOptions: {
        project: "./tsconfig.eslint.json",
        tsconfigRootDir: import.meta.dirname,
      },
    },
    rules: {
      "@typescript-eslint/explicit-function-return-type": "off",
      "@typescript-eslint/no-explicit-any": "warn",
      "@typescript-eslint/no-unused-vars": [
        "error",
        { argsIgnorePattern: "^_" },
      ],
      "@typescript-eslint/no-floating-promises": "error",
      "no-console": "off",
    },
  }
);

Prettier Configuration

.prettierrc

{
  "semi": true,
  "singleQuote": false,
  "tabWidth": 2,
  "trailingComma": "es5",
  "printWidth": 80,
  "arrowParens": "always"
}

Docker Configuration

Multi-Stage Dockerfile

See full Dockerfile in Docker Module.

docker-compose.yml

The generated file defines an app service, and a db service when you choose a database:

services:
  app:
    build: .
    ports:
      - "3000:3000"
    env_file:
      - .env
    environment:
      - NODE_ENV=production
    restart: unless-stopped

With a database, Compose also runs PostgreSQL or MongoDB and the app waits until it is healthy. See the Docker module docs.


Prisma Configuration

schema.prisma

generator client {
  provider = "prisma-client"
  output = "../src/generated/prisma"
  moduleFormat = "cjs"
}

datasource db {
  provider = "postgresql"
}

model User {
  id        String   @id @default(uuid())
  email     String   @unique
  name      String?
  password  String
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  @@map("users")
}

In Next.js projects, the datasource URL is loaded via prisma.config.ts. If Better Auth is enabled, the generated Prisma schema includes Better Auth tables (user, session, account, verification).

prisma.config.ts (Next.js)

import "dotenv/config";
import { defineConfig } from "prisma/config";

export default defineConfig({
  schema: "prisma/schema.prisma",
  migrations: {
    path: "prisma/migrations",
  },
  datasource: {
    // prisma generate needs no URL, so a build without .env (CI, a host) works.
    // Commands that connect report the empty URL
    url: process.env.DATABASE_URL ?? "",
  },
});

prisma/migrations

Run migrations:

# Create migration
npx prisma migrate dev --name add_user_table

# Apply migrations
npx prisma migrate deploy

# Reset database (careful!)
npx prisma migrate reset

Next.js Configuration

next.config.ts

Without Docker, the generated file is empty apart from the type import:

import type { NextConfig } from "next";

const nextConfig: NextConfig = {};

export default nextConfig;

If you choose Docker, it also sets output: "standalone", which the Docker image runs with node server.js.

Add options as you need them. See the Next.js configuration docs for available options.


Custom Configuration

Adding Custom Scripts

Edit package.json:

{
  "scripts": {
    "db:seed": "tsx prisma/seed.ts",
    "db:reset": "prisma migrate reset --force",
    "docker:build": "docker build -t myapp .",
    "docker:run": "docker run -p 3000:3000 myapp"
  }
}

Custom TypeScript Paths

Add to tsconfig.json:

{
  "compilerOptions": {
    "paths": {
      "@/*": ["./src/*"],
      "@/components/*": ["./src/components/*"],
      "@/lib/*": ["./src/lib/*"]
    }
  }
}

You can edit any of these configuration files after generation.

On this page