tsconfig-resolution
Problem
When running a TypeScript file with Bun from a different directory than where the tsconfig.json resides, Bun doesn’t pick up the TypeScript configuration. This causes emitDecoratorMetadata to not be applied, breaking NestJS dependency injection.
Symptoms
- Constructor-injected services are
undefined - Error:
undefined is not an object (evaluating 'this.service.method') - NestJS modules initialize successfully but commands/controllers fail
- Constructor is called before modules are initialized (visible in debug logs)
Reproduction
Monorepo Structure
project-root/├── package.json # workspaces: ["packages/*"]├── packages/│ └── my-cli/│ ├── package.json # name: "@project/my-cli"│ ├── tsconfig.json # emitDecoratorMetadata: true│ └── src/│ └── main.tsWhat Fails
# From project root - FAILSbun run packages/my-cli/src/main.ts# DI is broken, injected services are undefinedWhat Works
# From within package directory - WORKScd packages/my-clibun run src/main.ts
# Using workspace filter - WORKSbun run --filter @project/my-cli dev
# Building first, then running built output - WORKScd packages/my-clibun build src/main.ts --outdir dist --target bunbun run dist/main.jsRoot Cause
Bun’s TypeScript transpiler looks for tsconfig.json relative to the current working directory, not relative to the file being executed. When running from project root:
- Bun looks for
project-root/tsconfig.json - Doesn’t find
emitDecoratorMetadata: true(root tsconfig may not have it) - Transpiles without decorator metadata
- NestJS DI can’t resolve constructor parameter types
- Services are injected as
undefined
Solutions
Solution 1: Use Workspace Filter (Recommended)
In root package.json:
{ "scripts": { "my-cli": "bun run --filter @project/my-cli dev" }}In packages/my-cli/package.json:
{ "scripts": { "dev": "bun run src/main.ts" }}Usage:
bun run my-cli [args...]Why it works: The --filter flag runs the script from within the package’s directory context, where Bun finds the correct tsconfig.json.
Solution 2: Build Before Running
# Build with Bun's bundler (preserves decorator metadata)bun build src/main.ts --outdir dist --target bun --packages external
# Run built outputbun run dist/main.jsWhy it works: The bundler properly processes TypeScript with the correct tsconfig, and the built JavaScript includes the decorator metadata.
Solution 3: Root tsconfig with Path Reference
Create project-root/tsconfig.json:
{ "compilerOptions": { "experimentalDecorators": true, "emitDecoratorMetadata": true }}Note: This applies to ALL TypeScript files run from root, which may not be desirable.
Verification
Add debug logging to verify DI is working:
@Injectable()export class MyCommand extends CommandRunner { constructor(private readonly service: MyService) { super(); console.log(`DEBUG: service injected = ${!!service}`); }}Working output:
[Nest] ... [InstanceLoader] MyModule dependencies initializedDEBUG: service injected = trueBroken output:
DEBUG: service injected = false[Nest] ... [InstanceLoader] MyModule dependencies initializedNote how in the broken case, the constructor runs before modules initialize.
Related
- Bun + NestJS compatibility notes
- GitHub Issue: nestjs/nest#13881 (CLI support)
- Bun docs: TypeScript configuration
Key Takeaway
Always run Bun TypeScript files from the directory containing the tsconfig.json, or use workspace filters to ensure correct configuration resolution.