@atlex/core
v0.1.11
Published
IoC container, routing, middleware, events, and HTTP foundation for Atlex
Downloads
1,272
Maintainers
Keywords
Readme
@atlex/core
A robust, feature-rich framework core for building scalable Node.js applications
Installation
npm install @atlex/core
# or
yarn add @atlex/coreQuick Start
import { Application, Injectable, Controller, Get, Route } from '@atlex/core'
// Define a service
@Injectable()
export class UserService {
getUser(id: number) {
return { id, name: 'John Doe' }
}
}
// Define a controller
@Controller()
export class UserController {
constructor(private userService: UserService) {}
@Get('/users/:id')
getUser(request) {
const user = this.userService.getUser(request.params.id)
return { data: user }
}
}
// Bootstrap your application
const app = new Application()
// Register service providers
app.register(new UserServiceProvider())
// Register routes
Route.get('/health', async (request, response) => {
return { status: 'healthy' }
})
// Start listening
app.listen(3000, () => {
console.log('Server running on port 3000')
})Features
- Dependency Injection Container - Powerful IoC container with automatic resolution
- Flexible Routing - Fluent API and decorator-based route definitions
- Middleware Pipeline - Express-like middleware with request/response handling
- Service Providers - Structured application bootstrap and configuration
- Validation - Built-in request validation with customizable rules
- Event System - Publish/subscribe event dispatcher with decorator support
- Exception Handling - Centralized error handling and custom exceptions
- Decorators - Clean decorator syntax for controllers, routes, and dependency injection
- Request/Response Helpers - Convenient request/response access throughout application
- WebSocket Server - Room-based broadcasting with JWT auth on upgrade (v0.1.7)
Dependency Injection
The core of @atlex/core is its powerful dependency injection container that automatically resolves dependencies.
Container Binding
import { Container } from '@atlex/core'
const container = new Container()
// Bind to a concrete instance
container.bind('config', { apiKey: 'secret' })
// Bind to a singleton (resolved once, reused)
container.singleton('database', Database)
// Bind with a factory function
container.bind('logger', (container) => {
return new Logger(container.make('config'))
})
// Create an alias
container.alias('db', 'database')
// Make (resolve) from container
const db = container.make('database')Dependency Injection Decorators
import { Injectable, Singleton, Inject } from '@atlex/core'
// Mark class as injectable (transient - new instance each time)
@Injectable()
export class EmailService {
send(to: string, subject: string) {
// Send email logic
}
}
// Mark class as singleton (single instance throughout app)
@Singleton()
export class DatabaseConnection {
private connection
connect() {
this.connection = this.createConnection()
}
}
// Inject dependencies
@Injectable()
export class UserService {
constructor(
private emailService: EmailService,
private db: DatabaseConnection,
@Inject('config') private config: any,
) {}
registerUser(email: string) {
this.emailService.send(email, 'Welcome!')
// Save to database
}
}Advanced Container Usage
// Contextual binding - different implementations for different classes
container.contextualRule(PaymentProcessor, 'gateway', (container) => container.make(StripeGateway))
container.contextualRule(ReportGenerator, 'gateway', (container) => container.make(PayPalGateway))
// Check if binding exists
if (container.has('logger')) {
const logger = container.make('logger')
}
// Flush and reset container
container.flush()Routing
Define routes using the fluent API or decorators for a clean, expressive syntax.
Fluent Route API
import { Route } from '@atlex/core'
// Basic routes
Route.get('/users', (request, response) => {
return { users: [] }
})
Route.post('/users', (request, response) => {
const user = request.body
return { id: 1, ...user }
})
Route.put('/users/:id', (request, response) => {
const { id } = request.params
return { id, updated: true }
})
Route.patch('/users/:id', (request, response) => {
return { patched: true }
})
Route.delete('/users/:id', (request, response) => {
return { deleted: true }
})
// Route parameters and queries
Route.get('/users/:id', (request, response) => {
const userId = request.params.id
const filter = request.query.filter
return { userId, filter }
})
// Wildcard routes
Route.get('/files/*', (request, response) => {
return { path: request.params[0] }
})
// Named routes
Route.post('/users', (request, response) => {
return { id: 1 }
}).name('users.store')
// Route groups
Route.group({ prefix: '/api/v1' }, () => {
Route.get('/users', (request, response) => {
return { users: [] }
})
Route.post('/users', (request, response) => {
return { created: true }
})
})
Route.group({ prefix: '/admin', middleware: ['auth', 'admin'] }, () => {
Route.get('/dashboard', (request, response) => {
return { data: 'admin' }
})
})Decorator-Based Routes
import { Controller, Get, Post, Put, Delete, Patch } from '@atlex/core'
@Controller('/api/v1/users')
export class UserController {
constructor(private userService: UserService) {}
@Get('/:id')
show(request) {
return { user: this.userService.getUser(request.params.id) }
}
@Post()
store(request) {
return { created: this.userService.create(request.body) }
}
@Put('/:id')
update(request) {
return { updated: this.userService.update(request.params.id, request.body) }
}
@Patch('/:id')
patch(request) {
return { patched: true }
}
@Delete('/:id')
destroy(request) {
return { deleted: true }
}
}
// Register controller with application
app.resolveController(UserController)Middleware
import { Route } from '@atlex/core'
// Define middleware
const authMiddleware = async (request, response, next) => {
if (!request.headers.authorization) {
return response.status(401).json({ error: 'Unauthorized' })
}
request.user = { id: 1, name: 'John' }
return next()
}
const corsMiddleware = async (request, response, next) => {
response.setHeader('Access-Control-Allow-Origin', '*')
return next()
}
// Apply to routes
Route.get('/protected', authMiddleware, (request, response) => {
return { user: request.user }
})
// Apply to groups
Route.group({ middleware: [corsMiddleware] }, () => {
Route.get('/public', (request, response) => {
return { public: true }
})
})
// Apply globally
app.use(corsMiddleware)
app.use(authMiddleware)Service Providers
Service providers are the central place of all Atlex application bootstrap. They register bindings, boot services, and handle configuration.
import { ServiceProvider } from '@atlex/core'
export class DatabaseServiceProvider extends ServiceProvider {
// Register bindings into the container
register() {
this.app.container.singleton('database', () => {
return new Database({
host: process.env.DB_HOST,
port: process.env.DB_PORT,
username: process.env.DB_USER,
password: process.env.DB_PASSWORD,
})
})
}
// Boot the service (called after all services are registered)
boot() {
const db = this.app.container.make('database')
db.connect()
}
}
export class CacheServiceProvider extends ServiceProvider {
register() {
this.app.container.singleton('cache', () => {
return new CacheManager(this.app.container.make('config.cache'))
})
}
boot() {
const cache = this.app.container.make('cache')
cache.warmup()
}
}
// Register providers with application
const app = new Application()
app.register(new DatabaseServiceProvider())
app.register(new CacheServiceProvider())
app.boot()Validation
Validate request data with a powerful, fluent validation API.
import { validate, Validator, ValidationException, MessageBag } from '@atlex/core'
// Quick validation
Route.post('/users', async (request, response) => {
try {
const data = await validate(request.body, {
name: 'required|string|min:3',
email: 'required|email|unique:users',
age: 'integer|min:18',
})
return { user: data }
} catch (error) {
if (error instanceof ValidationException) {
return response.status(422).json({ errors: error.messages })
}
throw error
}
})
// Advanced validation
const validator = new Validator(request.body, {
name: 'required|string|max:255',
email: 'required|email',
password: 'required|min:8|confirmed',
website: 'url|nullable',
role: 'in:admin,user,guest',
permissions: 'array|min:1',
})
if (validator.fails()) {
const errors = validator.getMessageBag()
return response.status(422).json({ errors: errors.all() })
}
const validated = validator.validated()
// Custom validation rules
validator.addRule('even', (value) => {
return value % 2 === 0
})
validator.addRule('custom-domain', (value) => {
return value.endsWith('@company.com')
})
// Custom error messages
const validator2 = new Validator(
request.body,
{
email: 'required|email',
},
{
'email.required': 'An email address is required',
'email.email': 'Please provide a valid email address',
},
)Events
Publish and subscribe to events throughout your application using a clean event system.
import { Event, EventDispatcher, Listen } from '@atlex/core'
// Define an event
export class UserRegistered extends Event {
constructor(public user: User) {
super()
}
}
export class OrderCreated extends Event {
constructor(public order: Order) {
super()
}
}
// Listen to events with decorators
@Injectable()
export class SendWelcomeEmail {
@Listen(UserRegistered)
handle(event: UserRegistered) {
console.log(`Sending welcome email to ${event.user.email}`)
// Send email logic
}
}
@Injectable()
export class ProcessOrder {
@Listen(OrderCreated)
handle(event: OrderCreated) {
console.log(`Processing order ${event.order.id}`)
// Process order logic
}
}
// Dispatch events
const dispatcher = app.container.make(EventDispatcher)
dispatcher.dispatch(new UserRegistered(user))
dispatcher.dispatch(new OrderCreated(order))
// Listen to events programmatically
dispatcher.listen(UserRegistered, (event: UserRegistered) => {
console.log('User registered:', event.user)
})
dispatcher.listen(OrderCreated, (event: OrderCreated) => {
console.log('Order created:', event.order)
})Exception Handling
Handle exceptions globally with a centralized error handler.
import { Application, Exception } from '@atlex/core'
// Custom exception
export class AuthenticationException extends Exception {
constructor(message = 'Unauthenticated') {
super(message)
this.status = 401
}
}
export class AuthorizationException extends Exception {
constructor(message = 'Forbidden') {
super(message)
this.status = 403
}
}
// Global exception handler
app.onException((error, request, response) => {
if (error instanceof ValidationException) {
return response.status(422).json({
message: 'Validation failed',
errors: error.messages,
})
}
if (error instanceof AuthenticationException) {
return response.status(401).json({
message: error.message,
})
}
if (error instanceof AuthorizationException) {
return response.status(403).json({
message: error.message,
})
}
// Default error response
return response.status(500).json({
message: 'Internal Server Error',
error: process.env.NODE_ENV === 'development' ? error.message : undefined,
})
})
// Throw exceptions in routes/controllers
@Controller('/admin')
export class AdminController {
@Get('/dashboard')
dashboard(request) {
if (!request.user) {
throw new AuthenticationException('You must be logged in')
}
if (request.user.role !== 'admin') {
throw new AuthorizationException('You do not have permission to access this')
}
return { dashboard: 'data' }
}
}Request and Response Helpers
Access request and response objects with convenient global helpers.
import { request, response } from '@atlex/core'
// Access request properties
Route.post('/data', (req, res) => {
const body = req.body
const query = req.query
const params = req.params
const headers = req.headers
const method = req.method
const url = req.url
// Check request method
if (req.method === 'POST') {
// Handle POST
}
// Get header value
const contentType = req.header('Content-Type')
const bearerToken = req.header('Authorization')
// Get input from body or query
const name = req.input('name')
const page = req.input('page', 1) // with default
// Check if input exists
if (req.has('email')) {
const email = req.input('email')
}
return { received: true }
})
// Response helpers
Route.get('/data', (req, res) => {
// Send JSON
res.json({ data: 'value' })
// Send with status
res.status(201).json({ created: true })
// Set headers
res.setHeader('X-Custom', 'value')
res.setHeader('X-Total-Count', '100')
// Redirect
res.redirect('/home')
res.redirect(301, '/new-location')
// Download file
res.download('/path/to/file', 'filename.pdf')
// Send raw response
res.send('Hello World')
res.send(Buffer.from('binary data'))
})Application Class
The main Application class orchestrates your entire application lifecycle.
import { Application, ServiceProvider } from '@atlex/core'
const app = new Application({
basePath: __dirname,
environment: 'production',
})
// Register service providers
app.register(new DatabaseServiceProvider())
app.register(new CacheServiceProvider())
app.register(new EventServiceProvider())
// Boot all providers
app.boot()
// Make instances from container
const userService = app.make(UserService)
const config = app.make('config')
// Get the container directly
const container = app.container
// Get environment
const env = app.environment // 'production'
const isDev = app.isDev() // false
const isProd = app.isProd() // true
// Listen on port
app.listen(3000, () => {
console.log('Application running on port 3000')
})
// Resolve and boot controllers
app.resolveController(UserController)
app.resolveController(AdminController)
// Register global middleware
app.use((request, response, next) => {
console.log(`${request.method} ${request.url}`)
return next()
})
// Handle exceptions
app.onException((error, request, response) => {
console.error(error)
return response.status(500).json({ error: 'Server error' })
})
// Handle shutdown
process.on('SIGTERM', async () => {
await app.shutdown()
process.exit(0)
})WebSocket Server
@atlex/core v0.1.7 ships a room-based WebSocket module with JWT auth on the HTTP upgrade handshake. Invalid or missing tokens are rejected with 401 before the WebSocket handshake completes.
Setup
Install the ws peer dependency if not already present:
npm install ws
npm install --save-dev @types/wsDefine a Gateway
Extend WsGateway to handle connections and implement room logic:
import { WsGateway, WsClient } from '@atlex/core'
// Augment WsClientMeta with your JWT claim shape
declare module '@atlex/core' {
interface WsClientMeta {
userId: string
familyId: string
}
}
export class AppWsGateway extends WsGateway {
handleConnection(client: WsClient): void {
// Subscribe the client to rooms on connect
client.join(`family:${client.meta.familyId}`)
client.join(`device:${client.meta.userId}`)
}
handleDisconnect(_client: WsClient): void {
// Client is removed from all rooms automatically after this hook
}
}Register with the App
Pass WsServiceProvider a gateway class and a verifyToken callback before app.listen():
import { WsServiceProvider } from '@atlex/core'
import { AppWsGateway } from './AppWsGateway.js'
app.register(
new WsServiceProvider({
gateway: AppWsGateway,
verifyToken: async (token) => {
// Return decoded JWT claims — throw to reject the upgrade
return app.make(JwtProvider).verify(token)
},
}),
)
app.boot().listen(3000)The token is read from the Authorization: Bearer <token> header or the ?token= query parameter.
Broadcast from Anywhere
Resolve the gateway from the container and call broadcast:
const gw = app.make(AppWsGateway)
// Broadcast to everyone in a room
gw.broadcast('family:abc123', 'location:update', { lat: 40.7, lng: -74.0 })
// Send to a specific client
client.send('status:update', { online: true })Room Management
WsRoom tracks which clients are in which rooms. client.join / client.leave are scoped to the current client — use them inside handleConnection:
handleConnection(client: WsClient): void {
client.join('global')
client.join(`family:${client.meta.familyId}`)
}
// Check room size (via the injected WsRoom)
const room = app.make<WsRoom>('ws.room')
console.log(room.size('family:abc123')) // number of connected clientsWebSocket API Reference
WsGateway
| Method / Property | Description |
| --------------------------------- | ---------------------------------------- |
| handleConnection(client) | Abstract — called on successful JWT auth |
| handleDisconnect(client) | Abstract — called on socket close |
| broadcast(room, event, payload) | Send JSON event to all clients in a room |
WsClient
| Method / Property | Description |
| ---------------------- | ----------------------------------------- |
| socket | Raw ws.WebSocket instance |
| meta | Decoded JWT claims (WsClientMeta) |
| rooms | Set<string> of current room memberships |
| join(room) | Subscribe to a room (idempotent) |
| leave(room) | Unsubscribe from a room |
| send(event, payload) | Send a typed event to this client only |
WsServiceProvider options
| Option | Type | Description |
| ------------- | ------------------------------------------ | --------------------------------- |
| gateway | typeof WsGateway | Your gateway subclass to register |
| verifyToken | (token: string) => Promise<WsClientMeta> | Token validator; throw to reject |
API Overview
Container
| Method | Description |
| --------------------------------------------- | ---------------------------------------- |
| bind(key, value \| factory) | Bind a value or factory to the container |
| singleton(key, constructor \| factory) | Bind a singleton (single instance) |
| instance(key, instance) | Register a pre-built instance |
| alias(alias, key) | Create an alias for a binding |
| make(key) | Resolve and return a binding |
| contextualRule(concrete, abstract, factory) | Set contextual binding |
| has(key) | Check if binding exists |
| flush() | Clear all bindings |
Route
| Method | Description |
| -------------------------- | ----------------------------------- |
| get(path, handler) | Register GET route |
| post(path, handler) | Register POST route |
| put(path, handler) | Register PUT route |
| patch(path, handler) | Register PATCH route |
| delete(path, handler) | Register DELETE route |
| group(options, callback) | Group routes with shared attributes |
| middleware(names) | Apply middleware to route |
| name(name) | Give route a name |
Decorators
| Decorator | Target | Purpose |
| ---------------------- | --------- | ------------------------------ |
| @Injectable() | Class | Mark as injectable (transient) |
| @Singleton() | Class | Mark as singleton |
| @Inject(key?) | Parameter | Inject dependency |
| @Controller(prefix?) | Class | Define controller |
| @Get(path?) | Method | GET route handler |
| @Post(path?) | Method | POST route handler |
| @Put(path?) | Method | PUT route handler |
| @Patch(path?) | Method | PATCH route handler |
| @Delete(path?) | Method | DELETE route handler |
| @Listen(event) | Method | Listen to event |
| @Middleware(names) | Method | Apply middleware to handler |
Validation
| Class/Function | Purpose |
| ---------------------------------- | -------------------------------------- |
| validate(data, rules, messages?) | Quick validation function |
| Validator | Full validation class |
| ValidationException | Exception thrown on validation failure |
| MessageBag | Collection of validation messages |
Events
| Class | Purpose |
| --------------------- | ----------------------------- |
| Event | Base event class |
| EventDispatcher | Central event dispatcher |
| @Listen(eventClass) | Decorator to listen to events |
Configuration
Configure the application through environment variables and a configuration file.
// config/app.ts
export default {
name: 'Atlex Application',
environment: process.env.NODE_ENV || 'development',
debug: process.env.APP_DEBUG === 'true',
key: process.env.APP_KEY,
}
// config/database.ts
export default {
default: process.env.DB_CONNECTION || 'postgres',
connections: {
postgres: {
driver: 'postgres',
host: process.env.DB_HOST,
port: process.env.DB_PORT,
database: process.env.DB_NAME,
username: process.env.DB_USER,
password: process.env.DB_PASSWORD,
},
},
}
// Access configuration
const app = new Application()
const appConfig = app.make('config.app')
const dbConfig = app.make('config.database')Documentation
For detailed documentation, examples, and API reference, visit the Atlex documentation.
License
MIT
Part of Atlex — A modern framework for Node.js.
