Skip to content

Getting Started ​

This guide will help you install NeoSyringe and create your first container in 5 minutes.

Package Overview ​

NeoSyringe is split across three packages — you'll only ever install and import from the first two:

PackageWhat it's forYou import from it?
@djodjonx/neosyringeRuntime API: defineBuilderConfig, useInterface, useProperty, types (Container, BuilderConfig, ...)✅ Yes — this is what your application code imports
@djodjonx/neosyringe-pluginBuild integration: the Vite/Rollup/Webpack/esbuild plugin, and the tsc/ts-patch transformer✅ Yes — only in your build config (vite.config.ts, tsconfig.json, ...)
@djodjonx/neosyringe-coreInternal analyzer + code generator shared by the plugin, the LSP, and the CLI❌ No — this is an implementation detail. You should never need to import from it directly; if you find yourself doing so, something is probably better solved another way.

Installation ​

bash
pnpm add @djodjonx/neosyringe
pnpm add -D @djodjonx/neosyringe-plugin
bash
npm install @djodjonx/neosyringe
npm install -D @djodjonx/neosyringe-plugin
bash
yarn add @djodjonx/neosyringe
yarn add -D @djodjonx/neosyringe-plugin

Peer Dependencies

  • typescript >= 5.0 — including TypeScript 6
  • unplugin (required for build plugin)

NeoSyringe is fully compatible with TypeScript 6. No changes needed in your tsconfig.json.

Configure Your Build ​

NeoSyringe works with all major bundlers via unplugin, or directly with tsc via ts-patch — no bundler required.

typescript
// vite.config.ts
import { defineConfig } from 'vite';
import { neoSyringePlugin } from '@djodjonx/neosyringe-plugin';

export default defineConfig({
  plugins: [neoSyringePlugin.vite()]
});
typescript
// rollup.config.js
import { neoSyringePlugin } from '@djodjonx/neosyringe-plugin';

export default {
  plugins: [neoSyringePlugin.rollup()]
};
javascript
// webpack.config.js
module.exports = {
  plugins: [require('@djodjonx/neosyringe-plugin').webpack()]
};
javascript
// rspack.config.js
const { neoSyringePlugin } = require('@djodjonx/neosyringe-plugin');

module.exports = {
  plugins: [neoSyringePlugin.rspack()]
};
typescript
// esbuild.config.js
import { neoSyringePlugin } from '@djodjonx/neosyringe-plugin';

await esbuild.build({
  plugins: [neoSyringePlugin.esbuild()]
});
json
// tsconfig.json — no bundler needed, works with plain tsc
{
  "compilerOptions": {
    "plugins": [
      { "transform": "@djodjonx/neosyringe-plugin/transformer", "transformProgram": true }
    ]
  }
}
ts-patch setup

Install ts-patch and add a prepare script so it patches TypeScript automatically after npm/pnpm install:

bash
pnpm add -D ts-patch @djodjonx/neosyringe-plugin
json
// package.json
{
  "scripts": {
    "prepare": "ts-patch install -s"
  }
}

tsc and any tool that delegates to it (NestJS CLI's nest build, ts-node, etc.) will then pick up the transformer automatically.

NestJS 12+ with the Rspack builder

NestJS v12 deprecated its webpack builder in favor of Rspack (the default for monorepos, and available via --builder rspack otherwise). Point Nest at a custom Rspack config the same way you'd customize its webpack one — merge neoSyringePlugin.rspack() into the plugins array Nest already builds, rather than replacing the config outright:

javascript
// rspack.config.js
const { neoSyringePlugin } = require('@djodjonx/neosyringe-plugin');

module.exports = function (options, rspack) {
  return {
    ...options,
    plugins: [...options.plugins, neoSyringePlugin.rspack()],
  };
};

Verified end-to-end against a real Rspack build (@rspack/core) outside of Nest specifically — Nest's own CLI wiring (the exact nest-cli.json field name and CLI flag for a custom Rspack config path) can differ across versions, so check NestJS's own Rspack builder docs for the current option name in your version.

Create Your First Container ​

Step 1: Define Your Services ​

Create pure TypeScript classes and interfaces. No decorators needed!

typescript
// services/logger.ts
export interface ILogger {
  log(msg: string): void;
}

export class ConsoleLogger implements ILogger {
  log(msg: string) {
    console.log(`[LOG] ${msg}`);
  }
}
typescript
// services/user.service.ts
import type { ILogger } from './logger';

export class UserService {
  constructor(private logger: ILogger) {}

  createUser(name: string) {
    this.logger.log(`Creating user: ${name}`);
    return { id: crypto.randomUUID(), name };
  }
}

Step 2: Configure the Container ​

Best Practice

Put your container configuration in a dedicated file (e.g., container.ts). The plugin replaces the entire file content with generated code.

typescript
// container.ts
import { defineBuilderConfig, useInterface } from '@djodjonx/neosyringe';
import { ILogger, ConsoleLogger } from './services/logger';
import { UserService } from './services/user.service';

export const container = defineBuilderConfig({
  name: 'AppContainer',
  injections: [
    // Bind interface to implementation
    { token: useInterface<ILogger>(), provider: ConsoleLogger },
    
    // Autowire class (dependencies resolved automatically)
    { token: UserService }
  ]
});

Step 3: Use the Container ​

typescript
// main.ts
import { container } from './container';
import { UserService } from './services/user.service';

// Resolve with full type safety - no type assertions!
const userService = container.resolve(UserService);
// Type: UserService ✅ (automatically inferred)

const user = userService.createUser('John Doe');
// Full auto-completion available!

console.log(user); // { id: 'xxx-xxx', name: 'John Doe' }

Type Safety

The resolve() method automatically infers the return type from the token. No manual type assertions needed!

Project Structure ​

Recommended project structure:

src/
├── container.ts          # ✅ Container configuration
├── services/
│   ├── logger.ts         # Pure service
│   └── user.service.ts   # Pure service
└── main.ts               # Application entry

Tree Shaking

Import services directly instead of using barrel exports (index.ts) for better tree shaking in client-side code.

Development Mode ​

✅ The plugin works in dev mode with full HMR support!

The transform hook is called on every file change, so your container is regenerated instantly during development.

What's Next? ​

Released under the MIT License.