๐Ÿš€ Socket Launch Week Day 5:Introducing Repository Access Permissions and Custom Roles.Learn more โ†’
Sign In

najm-api

Package Overview
Dependencies
Maintainers
1
Versions
80
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

najm-api

Modern TypeScript decorator-based web framework built on Hono.js

npmnpm
Version
1.1.8
Version published
Weekly downloads
237
94.26%
Maintainers
1
Weekly downloads
ย 
Created
Source

Najm Framework ๐Ÿš€

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.

โœจ Key Features

  • ๐ŸŽฏ Decorator-Driven - Clean, declarative API with TypeScript decorators
  • ๐Ÿ”Œ Modular Plugin System - Install only what you need
  • ๐Ÿ’‰ Powerful DI Container - Advanced dependency injection with scopes (via diject)
  • ๐Ÿ›ก๏ธ Guards & Security - Function and class-based route protection
  • ๐Ÿ’พ Transaction Management - Automatic transactions with retry logic
  • ๐Ÿ“ก Event System - Built-in event emitter with decorator support
  • ๐ŸŒ i18n Support - Multi-language with automatic detection
  • ๐Ÿช Cookie Management - Secure cookie handling
  • ๐Ÿ”„ CORS Configuration - Flexible CORS setup
  • ๐Ÿ“ Built-in Logging - Structured logging with request ID tracking
  • ๐ŸŒฑ Database Seeding - Idempotent seeding with conflict resolution

๐Ÿ“ฆ Installation

Core Framework

# Core framework (required)
bun add najm diject hono reflect-metadata

# npm / yarn
npm install najm diject hono reflect-metadata

Plugins

Auto-registered plugins (included, no need to install separately):

  • najm-middleware - Middleware management
  • najm-params - Parameter resolution
  • najm-router - HTTP routing

Optional 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

๐Ÿ”ง Setup

TypeScript Configuration

{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "target": "ES2020",
    "module": "ESNext"
  }
}

Import reflect-metadata

import "reflect-metadata";

๐Ÿš€ Quick Start

Basic Server

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)

Next.js Integration

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 pointserver.listen(3000)handle(server) in catch-all route
Class discovery.scan('./src/features').load(featuresModule) with barrel imports
ConfigNoneserverExternalPackages in next.config.ts

๐Ÿ“– Core Concepts

Server API

// 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 management
  • params() - Parameter resolution
  • router() - HTTP routing

Optional plugins (must be explicitly registered):

  • guards(), database(), events(), cors(), cookies(), i18n(), auth(), etc.

Dependency Injection

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 {}

HTTP Routing

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 {}; }
}

Parameter Decorators

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: @Body(), @JsonBody(), @TextBody(), @FormData()
  • URL/Route: @Params(), @Query(), @Queries(), @Path(), @Url(), @Method()
  • Headers: @Headers(), @ContentType(), @Origin(), @Referer(), @Language()
  • Context: @Ctx(), @Req(), @Cookie(), @File(), @IP()
  • Guard Data: @User(), @Owner(), @Info(), @Data(), @Filter()

Guards & Authorization

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 };
  }
}

Database & Transactions

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;
  }
}

Event System

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}`);
  }
}

Validation & DTOs

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 invalid
  • Validator.check() - Business validation (unique, exists) โ†’ 404/409/403 if invalid
  • Service.execute() - Business logic

Internationalization

import { 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"
  }
}

Logging

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,
  }
});

Database Seeding

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:

  • Idempotent seeding (safe to run multiple times)
  • Conflict resolution strategies (skip, update)
  • Dependency-aware (seed users before products that reference them)
  • Built-in auth seeding with authSeed()

๐Ÿ—๏ธ Project Structure

Packages

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);

๐Ÿ”Œ Creating Custom Plugins

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);

๐Ÿ“š Complete Example

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);

๐Ÿงช Testing

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 });
});

๐Ÿ“– Documentation

Getting Started

Architecture

Core Concepts

Plugins

๐Ÿค Contributing

Contributions are welcome! Please submit a Pull Request.

๐Ÿ“ License

MIT License

๐Ÿ™ Acknowledgments

Najm is built on top of excellent open-source projects:

  • Hono - The ultrafast web framework for the Edges
  • diject - Standalone dependency injection container (originally developed for Najm, now maintained as an independent package)
  • mitt - Tiny functional event emitter

Keywords

najm

FAQs

Package last updated on 07 May 2026

Did you know?

Socket

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.

Install

Related posts