
Research
/Security News
Miasma Mini Shai-Hulud Hits ImmobiliareLabs npm Packages
Miasma Mini Shai-Hulud hits @immobiliarelabs Backstage plugins, targeting GitLab and LDAP auth packages on npm.
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 107 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.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Research
/Security News
Miasma Mini Shai-Hulud hits @immobiliarelabs Backstage plugins, targeting GitLab and LDAP auth packages on npm.

Security News
Rolldown paused Rust React Compiler integration after a 5MB binary size increase raised concerns about shipping React-specific code to all Vite users.

Security News
/Research
Mini Shai-Hulud expands into the Go ecosystem after hitting LeoPlatform npm packages and targeting GitHub Actions workflows.