A modern, type-safe, and lightweight ecosystem for building scalable TypeScript applications
AssemblerJS is a complete ecosystem for building modular and type-safe TypeScript applications. The monorepo provides:
- 🎯 assemblerjs - A modern dependency injection system with lifecycle hooks and events
- 🌐 @assemblerjs/rest - REST framework with Express.js decorators
- 📦 @assemblerjs/dto - DTO validation and transformation with class-validator
- 🔌 @assemblerjs/electron - Electron integration with type-safe IPC
- 🌐 @assemblerjs/fetch - HTTP decorators for fetch requests
- 🗄️ @assemblerjs/mongo - MongoDB integration with Mongoose
Inspired by DIOD and NestJS, the name AssemblerJS pays tribute to Gilles Deleuze and Felix Guattari's concept of Agencement, translated in English as Assemblage.
npm install assemblerjs reflect-metadata
# or
yarn add assemblerjs reflect-metadataimport 'reflect-metadata';
import { Assemblage, Assembler, AbstractAssemblage } from 'assemblerjs';
// Define a service
@Assemblage()
class Logger implements AbstractAssemblage {
log(message: string) {
console.log(`[LOG] ${message}`);
}
}
// Define a service that depends on Logger
@Assemblage({
inject: [[Logger]], // Declare dependencies
})
class UserService implements AbstractAssemblage {
constructor(private logger: Logger) {}
getUser(id: string) {
this.logger.log(`Fetching user ${id}`);
return { id, name: 'John Doe' };
}
}
// Define an application
@Assemblage({
inject: [[UserService]],
})
class App implements AbstractAssemblage {
constructor(private userService: UserService) {}
async onInit() {
const user = this.userService.getUser('123');
console.log(user);
}
}
// Bootstrap the application
const app = Assembler.build(App);
// Output: "[LOG] Fetching user 123"
// Output: "{ id: '123', name: 'John Doe' }"The core dependency injection system with support for lifecycle hooks, events, and tags.
Features:
- Type-safe dependency injection with decorators
- Lifecycle hooks (
onRegister,onInit,onDispose) - Built-in event system
- AOP/Transversals for cross-cutting concerns (logging, security, caching)
- Singleton & Transient scopes
- Custom decorators
- Tree-shakable (~5-35 KB depending on usage)
📖 Documentation complète • README
REST framework for Express.js with type-safe decorators for controllers, routes, and middleware.
Use case: Create REST APIs with Express.js in a declarative way
import { Controller, Get, Post, Body } from '@assemblerjs/rest';
@Controller('/users')
class UserController {
@Get('/:id')
getUser(@Param('id') id: string) {
return { id, name: 'John' };
}
@Post()
createUser(@Body() data: CreateUserDto) {
return { id: '123', ...data };
}
}HTTP decorators to simplify fetch calls with parameters, queries, and automatic parsing.
Use case: Create declarative HTTP clients
import { Fetch, Query, Param, Parse } from '@assemblerjs/fetch';
class UserApiClient {
@Fetch('get', 'https://api.example.com/users')
@Parse('json')
async getUsers(
@Query('limit') limit: number,
@Query('skip') skip: number,
data?: any
) {
return data;
}
@Fetch('get', 'https://api.example.com/users/:id')
async getUser(@Param('id') id: string, data?: any) {
return data;
}
}Electron integration with type-safe IPC between main process, renderer, and preload.
Use case: Build Electron applications with AssemblerJS
MongoDB integration with Mongoose and decorators to define schemas and models.
Use case: Work with MongoDB in AssemblerJS
import { Schema, Prop, Model } from '@assemblerjs/mongo';
@Schema()
class User {
@Prop({ required: true })
name: string;
@Prop()
email: string;
}
const UserModel = Model(User);DTO validation and transformation using class-validator and class-transformer.
Use case: Validate and transform data transfer objects
import { DTO } from '@assemblerjs/dto';
import { IsString, IsEmail } from 'class-validator';
@DTO()
class CreateUserDto {
@IsString()
name: string;
@IsEmail()
email: string;
}Shared internal utilities (types, collections, errors). Automatically installed with assemblerjs.
@Assemblage()
class DatabaseService implements AbstractAssemblage {
private connection: any;
// Called when registering (static)
static onRegister(context: AssemblerContext) {
console.log('DatabaseService registered');
}
// Called when instance is ready
async onInit() {
this.connection = await connectToDatabase();
console.log('Database connected');
}
// Called on cleanup
async onDispose() {
await this.connection.close();
console.log('Database disconnected');
}
}import { EventManager, Assemblage, Context, AssemblerContext } from 'assemblerjs';
const Events = {
USER_CREATED: 'app:user:created',
};
@Assemblage({
events: Object.values(Events), // Register events
})
class UserService extends EventManager {
createUser(name: string) {
const user = { id: '123', name };
this.emit(Events.USER_CREATED, user); // Emit event
return user;
}
}
@Assemblage({
inject: [[UserService]],
})
class NotificationService {
constructor(
private userService: UserService,
@Context() private context: AssemblerContext
) {
// Subscribe to events via context
this.context.on(Events.USER_CREATED, (user) => {
console.log(`Welcome ${user.name}!`);
});
}
}import { ParameterDecoratorFactory } from 'assemblerjs';
// Create a custom parameter decorator
const CurrentUser = ParameterDecoratorFactory((context, args) => {
return { id: '123', name: 'John' }; // Resolve current user
});
@Assemblage()
class UserController {
getProfile(@CurrentUser() user: User) {
return user;
}
}@Assemblage({ tags: ['plugin', 'logger'] })
class ConsoleLogger implements AbstractAssemblage {
log(msg: string) { console.log(msg); }
}
@Assemblage({ tags: ['plugin', 'logger'] })
class FileLogger implements AbstractAssemblage {
log(msg: string) { /* write to file */ }
}
@Assemblage()
class App {
@Context() context!: AssemblerContext;
onInit() {
// Get all loggers
const loggers = this.context.getByTag<AbstractAssemblage>('logger');
loggers.forEach(logger => logger.log('Hello'));
}
}import 'reflect-metadata';
import { Assemblage, Assembler, AbstractAssemblage, Context, AssemblerContext } from 'assemblerjs';
// Configuration
@Assemblage()
class Config implements AbstractAssemblage {
readonly apiUrl = 'https://api.example.com';
readonly port = 3000;
}
// Database Service
@Assemblage()
class Database implements AbstractAssemblage {
private connection: any;
async onInit() {
this.connection = await this.connect();
console.log('✓ Database connected');
}
async onDispose() {
await this.connection?.close();
console.log('✓ Database disconnected');
}
private async connect() {
// Connect to database
return { /* connection */ };
}
async query(sql: string) {
return this.connection.query(sql);
}
}
// User Repository
@Assemblage({
inject: [[Database]],
})
class UserRepository implements AbstractAssemblage {
constructor(private db: Database) {}
async findById(id: string) {
return this.db.query(`SELECT * FROM users WHERE id = ${id}`);
}
async create(data: any) {
return this.db.query(`INSERT INTO users ...`);
}
}
// User Service with Events
@Assemblage({
inject: [[UserRepository]],
events: ['app:user:created'],
})
class UserService extends EventManager {
constructor(private userRepo: UserRepository) {
super();
}
async createUser(data: any) {
const user = await this.userRepo.create(data);
this.emit('app:user:created', user);
return user;
}
}
// Application
@Assemblage({
inject: [[Config, UserService]],
})
class App implements AbstractAssemblage {
constructor(
private config: Config,
private userService: UserService,
@Context() private context: AssemblerContext
) {
// Subscribe to events
this.context.on('app:user:created', (user) => {
console.log('New user created:', user);
});
}
async onInit() {
console.log(`✓ App started on port ${this.config.port}`);
// Create a user
await this.userService.createUser({
name: 'John Doe',
email: 'john@example.com'
});
}
@Dispose()
async shutdown(dispose: () => Promise<void>) {
console.log('Shutting down...');
await dispose(); // Calls onDispose on all assemblages
}
}
// Bootstrap
const app = Assembler.build(App);Comprehensive documentation is available in the docs directory:
-
Getting Started
-
Core Concepts
-
Features
-
API Reference
assemblerjs/
├── packages/
│ ├── assemblerjs/ # Core DI library
│ ├── core/ # Internal utilities
│ ├── dto/ # DTO validation
│ ├── electron/ # Electron integration
│ ├── fetch/ # HTTP decorators
│ ├── mongo/ # MongoDB integration
│ └── rest/ # REST framework
├── docs/ # Documentation
└── README.md # This file
This monorepo uses Nx for build orchestration and Yarn as package manager.
- Node.js ≥ 18.12.0
- Yarn ≥ 1.22.0
# Install dependencies
yarn install
# Build all packages
npx nx run-many -t build
# Test all packages
npx nx run-many -t test
# Build specific package
npx nx build assemblerjs
# Test specific package
npx nx test assemblerjsAssemblerJS is optimized for performance:
- Assembler Building: 156k ops/sec
- Singleton Cache: 1.2M ops/sec
- Event Emission: 432k ops/sec
- Decorators: 890k ops/sec
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
MIT
- Inspired by DIOD and NestJS
- Philosophical concept from Gilles Deleuze and Felix Guattari's Agencement
Made with ❤️ in Marseille