
Company News
Jerod Santo Joins Socket as Head of Media
Allow myself to introduce... myself.
A modern, modular TypeScript framework for building scalable APIs with decorators, dependency injection, and plugin architecture.
Najm is built on Hono.js and uses diject for dependency injection. It provides a powerful decorator-driven development experience with first-class support for transactions, events, guards, i18n, and more.
# Core framework (required)
bun add najm diject hono reflect-metadata
# npm / yarn
npm install najm diject hono reflect-metadata
Auto-registered plugins (included, no need to install separately):
najm-middleware - Middleware managementnajm-params - Parameter resolutionnajm-router - HTTP routingOptional plugins (install as needed):
# Authorization
bun add najm-guard
# Database & Transactions
bun add najm-database
# Event System
bun add najm-event
# CORS
bun add najm-cors
# Cookie Management
bun add najm-cookies
# Internationalization
bun add najm-i18n
# Install all optional plugins
bun add najm-guard najm-database najm-event najm-cors najm-cookies najm-i18n
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"target": "ES2020",
"module": "ESNext"
}
}
import "reflect-metadata";
import "reflect-metadata";
import { Server } from "najm";
import { Service, Controller } from "diject";
import { Get, Post } from "najm-router";
import { Body, Params } from "najm-params";
@Service()
class UserService {
getUsers() {
return [{ id: 1, name: "John" }, { id: 2, name: "Jane" }];
}
}
@Controller("/users")
class UserController {
constructor(private userService: UserService) {}
@Get("/")
getUsers() {
return this.userService.getUsers();
}
@Get("/:id")
getUser(@Params("id") id: string) {
return { id, name: "User " + id };
}
@Post("/")
createUser(@Body() data: any) {
return { created: true, ...data };
}
}
// One-line server creation and startup
await new Server()
.load(UserController, UserService)
.log("π Server starting on port 3000")
.listen(3000);
import "reflect-metadata";
import { Server } from "najm";
import { Service, Controller, Repository } from "diject";
import { guards } from "najm-guard";
import { database } from "najm-database";
import { events } from "najm-event";
import { cors } from "najm-cors";
import { cookies } from "najm-cookies";
import { i18n } from "najm-i18n";
// One-line server creation with plugins and auto-scanning
await new Server()
.use(cors({ origin: "*" }))
.use(database({ default: myDb }))
.use(i18n({ translations: { en, fr } }))
.use(guards())
.use(events())
.base("/api")
.scan("./src/features") // Auto-discover controllers
.log("β
Plugins configured")
.log("π Starting server...")
.listen(3000);
// Alternative: Manual loading instead of .scan()
// .load(UserController, UserService, UserRepository, AuthGuard)
Najm works as an API backend inside Next.js App Router using a catch-all route.
1. Configure next.config.ts:
reflect-metadata and native database drivers must be externalized from the Next.js bundle:
// next.config.ts
import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
serverExternalPackages: ['reflect-metadata', 'better-sqlite3'],
};
export default nextConfig;
2. Create a shared server instance:
Use .load() with barrel imports instead of .scan() β bundlers can't resolve dynamic filesystem imports.
// src/server.ts
import 'reflect-metadata';
import { Server } from 'najm';
import { database } from 'najm-database';
import * as features from './features';
export const server = new Server()
.use(database({ default: db }))
.base('/api')
.load(features);
3. Create the catch-all API route:
// app/api/[...route]/route.ts
import { handle } from 'najm';
import { server } from '@/server';
export const GET = handle(server);
export const POST = handle(server);
export const PUT = handle(server);
export const PATCH = handle(server);
export const DELETE = handle(server);
handle() wraps server.fetch for Next.js route handlers. The server auto-initializes on the first request.
| Standalone (Bun) | Next.js | |
|---|---|---|
| Entry point | server.listen(3000) | handle(server) in catch-all route |
| Class discovery | .scan('./src/features') | .load(featuresModule) with barrel imports |
| Config | None | serverExternalPackages in next.config.ts |
// Constructor
const server = new Server(opts?) // Optional config: { app, port, isolated, ... }
// Chainable methods
server.use(plugin) // Register plugin
server.load(...classes) // Register app classes
server.scan(path) // Scan directories for classes (e.g., './src/features')
server.base(path) // Set base path prefix
server.middleware(...handlers) // Add middleware handlers
server.set(key, value) // Set server options
server.log(...messages) // Log messages (chainable)
await server.listen(port, cb?) // Start server (returns this)
await server.init() // Initialize without listening (for seeding, testing)
// Serverless
const handler = server.fetch // Auto-initializes, returns fetch handler
// State management
server.isRunning // boolean - Server running state
await server.stop() // Stop the server gracefully
Default plugins (auto-registered):
middleware() - Middleware managementparams() - Parameter resolutionrouter() - HTTP routingOptional plugins (must be explicitly registered):
guards(), database(), events(), cors(), cookies(), i18n(), auth(), etc.Najm uses diject - a standalone, framework-agnostic dependency injection container with decorators, scopes (SINGLETON, REQUEST, TRANSIENT), and AsyncLocalStorage support.
import { Service, Controller, Injectable } from "diject";
// Singleton service (default)
@Service()
class ConfigService {
getConfig() { return { apiUrl: "..." }; }
}
// Controller
@Controller("/api")
class ApiController {
constructor(private config: ConfigService) {}
}
// Generic injectable
@Injectable()
class MyComponent {}
import { Controller } from "diject";
import { Get, Post, Put, Patch, Delete } from "najm-router";
@Controller("/api/users")
class UserController {
@Get("/") // GET /api/users
getAll() { return []; }
@Get("/:id") // GET /api/users/:id
getById() { return {}; }
@Post("/") // POST /api/users
create() { return {}; }
@Put("/:id") // PUT /api/users/:id
update() { return {}; }
@Patch("/:id") // PATCH /api/users/:id
patch() { return {}; }
@Delete("/:id") // DELETE /api/users/:id
remove() { return {}; }
}
import { Body, Params, Query, Headers, Ctx, Cookie } from "najm-params";
@Controller("/api")
class DataController {
@Get("/users/:id")
getUser(
@Params("id") id: string, // URL parameter
@Query("page") page: string, // Query parameter
@Headers("authorization") auth: string, // Header
@Cookie("session") session: string, // Cookie
@Body() body: any, // Request body
@Ctx() ctx: any // Hono context
) {
return { id, page, auth, session };
}
}
Available parameter decorators:
@Body(), @JsonBody(), @TextBody(), @FormData()@Params(), @Query(), @Queries(), @Path(), @Url(), @Method()@Headers(), @ContentType(), @Origin(), @Referer(), @Language()@Ctx(), @Req(), @Cookie(), @File(), @IP()@User(), @Owner(), @Info(), @Data(), @Filter()import { Service, Container, DI } from "diject";
import { Guards } from "najm-guard";
import { Headers } from "najm-params";
import { USER } from "najm-guard";
// Function guard
const authGuard = async (headers, cookie, context) => {
const token = headers("authorization");
if (!token) throw new Error("Unauthorized");
return { userId: "123" }; // Available via @GuardParams
};
// Class guard
@Service()
class RoleGuard {
@DI() container!: Container;
async canActivate(@Headers("authorization") token: string) {
const user = await this.verifyToken(token);
this.container.set(USER, user); // Set ALS token
return true;
}
}
@Controller("/admin")
@Guards(authGuard, RoleGuard) // Controller-level
class AdminController {
@Get("/users")
@Guards(adminOnlyGuard) // Method-level
getUsers(@User() user: any) {
return { users: [], currentUser: user };
}
}
import { Repository, Service } from "diject";
import { DB, Transaction } from "najm-database";
@Repository("postgres")
class UserRepository {
@DB("postgres") db: any; // Auto-injected with transaction awareness
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 ...", [data]);
}
}
@Service()
class OrderService {
constructor(
private orderRepo: OrderRepository,
private inventoryRepo: InventoryRepository
) {}
@Transaction({ retries: 2 }) // Auto retry on deadlock
async createOrder(data: any) {
// All DB operations use same transaction
const order = await this.orderRepo.create(data);
await this.inventoryRepo.decrementStock(data.items);
return order;
}
}
import { Service } from "diject";
import { Events, On } from "najm-event";
@Service()
class UserService {
@Events() events: { emit, on, off };
async createUser(data: any) {
const user = await this.repo.create(data);
this.events.emit("user.created", { userId: user.id });
return user;
}
}
@Service()
class EmailService {
@On("user.created") // Auto-registered as event listener
async sendWelcome({ userId }: { userId: string }) {
console.log(`Sending welcome email to user ${userId}`);
}
}
Najm uses Zod-based DTOs (Data Transfer Objects) for request validation via the najm-validation plugin. DTOs define the expected shape and constraints of incoming data, while Validators handle async business rules.
import { z } from "zod";
// Define a DTO with Zod
export const createProductDto = z.object({
name: z.string().min(2, "Name must be at least 2 characters"),
price: z.number().positive("Price must be positive"),
category: z.string().optional(),
});
export const updateProductDto = createProductDto.partial();
export const productIdParam = z.object({
id: z.string().length(5, "Product ID must be 5 characters"),
});
// Infer TypeScript types from schemas
export type CreateProductDto = z.infer<typeof createProductDto>;
export type UpdateProductDto = z.infer<typeof updateProductDto>;
Use @Validate() in controllers to auto-validate requests:
import { Controller, Post, Patch } from "najm-router";
import { Validate } from "najm-validation";
import { Body, Params } from "najm-params";
import { createProductDto, updateProductDto, productIdParam } from "./product.dto";
@Controller("/products")
class ProductController {
@Post("/")
@Validate(createProductDto) // Validates body
create(@Body() data: CreateProductDto) {
return this.service.create(data);
}
@Patch("/:id")
@Validate({ params: productIdParam, body: updateProductDto }) // Validates params + body
update(@Params("id") id: string, @Body() data: UpdateProductDto) {
return this.service.update(id, data);
}
}
Validation flow:
@Validate(dto) - Schema validation (format, length, type) β 400 if invalidValidator.check() - Business validation (unique, exists) β 404/409/403 if invalidService.execute() - Business logicimport { Controller, Service } from "diject";
import { I18n } from "najm-i18n";
import { Get, Query } from "najm-router";
@Controller("/api")
class ApiController {
@I18n() t: any; // Translation function
@Get("/greeting")
getGreeting(@Query("name") name: string) {
return {
message: this.t("welcome", { name }),
lang: this.t.getCurrentLanguage()
};
}
}
@Service()
class NotificationService {
@I18n("errors") t: any; // With prefix
sendError() {
return this.t("notFound"); // Translates "errors.notFound"
}
}
Najm includes a built-in logging system with support for different log levels, formats, and request ID tracking.
import { Service } from "diject";
import { Log } from "najm";
@Service()
class UserService {
@Log() logger!: LoggerService; // Property injection
async createUser(data: any) {
this.logger.info("Creating user", { email: data.email });
try {
const user = await this.repo.create(data);
this.logger.debug("User created successfully", { userId: user.id });
return user;
} catch (error) {
this.logger.error("Failed to create user", error, { email: data.email });
throw error;
}
}
}
// Server-level logging (chainable)
const server = new Server()
.log("π Starting server...")
.use(database({ default: db }))
.log("β
Database configured")
.load(UserController, UserService);
await server.listen(3000);
Log Levels: DEBUG, INFO, WARN, ERROR, SILENT
Configuration:
const server = new Server({
logger: {
level: 'INFO',
format: 'pretty', // or 'json' for production
includeTimestamp: true,
includeRequestId: true,
colors: true,
}
});
Najm provides a powerful seeding system via SeedService from najm-database.
import { Server } from "najm";
import { SeedService } from "najm-database";
import { authSeed } from "najm-auth"; // Pre-built auth seeding helper
import { database } from "najm-database";
const server = await new Server({ isolated: true })
.use(database({ default: db }))
.scan('./src/features')
.log("π± Seeding database...")
.init(); // Initialize without listening
const seeder = server.container.get(SeedService);
const report = await seeder.run(
{
// Auth plugin provides authSeed helper
...authSeed({
adminEmail: 'admin@example.com',
adminPass: 'Admin123!',
roles: [
{ name: 'admin', description: 'Administrator' },
{ name: 'user', description: 'Regular user' },
],
permissions: [
{ action: 'create', resource: 'posts', name: 'create:posts' },
{ action: 'read', resource: 'posts', name: 'read:posts' },
],
}),
// Custom table seeding
products: {
by: ['id'], // Unique key for conflict resolution
rows: [
{ id: '1', name: 'Product 1', price: 99.99 },
{ id: '2', name: 'Product 2', price: 149.99 },
],
},
},
{
verbose: true,
onConflict: 'skip', // or 'update'
transaction: false,
}
);
server
.log(`β
Seed complete`)
.log(`π Total operations: ${report.items.length}`)
.log('π Test user: admin@example.com / Admin123!');
await server.stop();
Benefits:
skip, update)authSeed()packages/
βββ najm/ # Public-facing framework package (this package)
βββ najm-core/ # Framework core & orchestration
βββ najm-guard/ # Authorization plugin (optional)
βββ najm-database/ # Database & transactions plugin (optional)
βββ najm-event/ # Event system plugin (optional)
βββ najm-cors/ # CORS handling plugin (optional)
βββ najm-cookies/ # Cookie management plugin (optional)
βββ najm-i18n/ # Internationalization plugin (optional)
βββ najm-auth/ # Authentication plugin (optional)
βββ najm-validation/ # Validation plugin (optional)
βββ najm-rate/ # Rate limiting plugin (optional)
βββ najm-email/ # Email service plugin (optional)
βββ najm-cache/ # Cache management plugin (optional)
Note: Dependency injection is provided by diject, an external standalone package.
Feature-based structure (recommended for scalability):
src/
βββ config/ # Plugin configurations
β βββ database.ts # Database plugin config
β βββ auth.ts # Auth plugin config
β βββ plugins.ts # Export all configs
βββ features/ # Feature modules (auto-discovered by .scan())
β βββ user/
β β βββ user.controller.ts
β β βββ user.dto.ts # Zod validation schemas + inferred types
β β βββ user.validator.ts # Business validation (uniqueness, existence)
β β βββ user.service.ts
β β βββ user.repository.ts
β β βββ index.ts
β βββ product/
β βββ product.controller.ts
β βββ product.dto.ts
β βββ product.service.ts
β βββ index.ts
βββ database/ # Centralized database schema & setup
β βββ schema.ts # All table definitions (+ authSchema if using najm-auth)
β βββ seed.ts # Seeding script
βββ listeners/ # Event listeners
β βββ user.listener.ts
βββ locales/ # i18n translations
β βββ en.ts
β βββ fr.ts
βββ main.ts # Server entry point
Example main.ts:
import 'reflect-metadata';
import { Server } from 'najm';
import {
databaseConfig,
authConfig,
corsConfig,
i18nConfig
} from './config/plugins';
import { ProductListener } from './listeners';
const PORT = Number.parseInt(process.env.PORT || '3000', 10);
await new Server()
.use(corsConfig())
.use(databaseConfig())
.use(i18nConfig())
.use(authConfig())
.base('/api')
.scan('./src/features') // Auto-discover all controllers
.load(ProductListener) // Manual registration when needed
.log('β
Server configured')
.log(`π Starting on port ${PORT}`)
.listen(PORT);
Najm provides a fluent API for creating plugins using the plugin() builder:
import { plugin, Meta, Service, Inject } from "najm";
// Define plugin token
export const MY_PLUGIN_CONFIG = Symbol("MY_PLUGIN_CONFIG");
// Create service
@Service()
@Meta({ layer: "plugin", order: 20 })
class MyPluginService {
@Inject(MY_PLUGIN_CONFIG) config: any;
async scan() {
// Read decorators, push to INJECTIONS
}
async configure() {
// Setup connections, register injectors
}
async activate() {
// Wire to app
}
async onReady() {
// Post-boot actions
}
}
// Create plugin factory using fluent API
export const myPlugin = (config?: any) =>
plugin("my-plugin")
.version("1.0.0")
.services(MyPluginService)
.config(MY_PLUGIN_CONFIG, config ?? {})
.build();
// Advanced plugin with dependencies and contributions
export const advancedPlugin = (config?: any) =>
plugin("advanced-plugin")
.version("2.0.0")
.depends(otherPlugin()) // Auto-registered dependencies
.requires("database") // Required dependencies (user must register)
.contributes(TOKEN, value) // Contribute to other plugins
.services(PluginService)
.config(PLUGIN_CONFIG, config)
.set(EXTRA_TOKEN, extraValue) // Additional tokens
.build();
// Usage
const server = new Server()
.use(myPlugin({ option: "value" }))
.load(AppController);
import "reflect-metadata";
import { Server } from "najm";
import { Service, Controller, Repository, DI, Container } from "diject";
import { guards } from "najm-guard";
import { database } from "najm-database";
import { events } from "najm-event";
import { Get, Post, Body, Params, Headers } from "najm-router";
import { Guards } from "najm-guard";
import { DB, Transaction } from "najm-database";
import { Events, On } from "najm-event";
import { USER } from "najm-guard";
// Repository
@Repository("postgres")
class UserRepository {
@DB("postgres") db: any;
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 VALUES (?)", [data]);
}
}
// Service
@Service()
class UserService {
constructor(private repo: UserRepository) {}
@Events() events: any;
@Transaction({ retries: 2 })
async createUser(data: any) {
const user = await this.repo.create(data);
this.events.emit("user.created", { userId: user.id });
return user;
}
}
// Guard
@Service()
class AuthGuard {
@DI() container!: Container;
async canActivate(@Headers("authorization") token: string) {
const user = await this.verifyToken(token);
this.container.set(USER, user);
return true;
}
}
// Event listener
@Service()
class EmailService {
@On("user.created")
async sendWelcome({ userId }: any) {
console.log(`Welcome email sent to user ${userId}`);
}
}
// Controller
@Controller("/api/users")
@Guards(AuthGuard)
class UserController {
constructor(private userService: UserService) {}
@Get("/:id")
async getUser(@Params("id") id: string) {
return this.userService.findById(id);
}
@Post("/")
async createUser(@Body() data: any) {
return this.userService.createUser(data);
}
}
// Server - one-line creation with all components
await new Server()
.use(database({ default: postgresDb }))
.use(guards())
.use(events())
.load(UserController, UserService, UserRepository, AuthGuard, EmailService)
.log("β
All services loaded")
.log("π Server starting...")
.listen(3000);
import { describe, test, expect, afterEach } from "bun:test";
import { Server } from "najm";
import { Controller } from "diject";
import { Get } from "najm-router";
let server: Server;
afterEach(async () => { await server?.stop(); });
test("should handle GET request", async () => {
@Controller("/test")
class TestController {
@Get("/") get() { return { ok: true }; }
}
server = await new Server({ isolated: true })
.load(TestController)
.listen(3100);
const res = await fetch("http://localhost:3100/test");
expect(await res.json()).toEqual({ ok: true });
});
Contributions are welcome! Please submit a Pull Request.
MIT License
Najm is built on top of excellent open-source projects:
FAQs
Modern TypeScript decorator-based web framework built on Hono.js
The npm package najm-api receives a total of 42 weekly downloads. As such, najm-api popularity was classified as not popular.
We found that najm-api demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago.Β It has 1 open source maintainer collaborating on the project.

Company News
Allow myself to introduce... myself.

Research
/Security News
A Twitch browser extension on Chrome and Firefox forwards usersβ live OAuth session tokens through proxies controlled by a Russian bot service.

Security News
Anthropic found biased reasoning and recklessness drove Claude Mythos 5 to publish malware on PyPI and compromise a security vendor.