@nilejs/nile

中文文档
1.0.4发布于 2026/11/02MITTypeScriptESM + CJS

TypeScript-first, service and actions oriented backend framework

原始使用文档
文档目录21 节
  1. Upgrading
  2. Install
  3. Start from the CLI (recommended)
  4. Add to an existing project
  5. Quick start
  6. 1. Write an action
  7. 2. Group actions into a service
  8. 3. Start the server
  9. 4. Call it
  10. Action input and responses
  11. Project structure
  12. Results and utilities
  13. Hooks
  14. What else is included
  15. Startup statistics
  16. Schema conversion
  17. Hook execution context
  18. Using with coding assistants
  19. Requirements
  20. Contributing
  21. License

Nile Js

NPM Version License: MIT

A functional TypeScript backend for small teams shipping growing products.

You write actions, group them into services, and Nile Js gives you a predictable API with validation, error handling and schema export. There are no route trees, controllers or middleware chains to maintain: each business operation lives in one well known place, so small teams keep shipping as the backend grows.

Documentation · Getting started · Features · Examples

Upgrading

Read the migration notes before upgrading an existing application.

Install

bunx @nilejs/cli new my-app
cd my-app
bun install
bun run dev

The starter runs on Bun's native HTTP server with an embedded PGlite database, a tasks service and generators for new services and actions. See @nilejs/cli.

Add to an existing project

bun add @nilejs/nile zod

Results, Options and safeTry come from @nilejs/nile/fp, which re-exports slang-ts.

For the database helpers (createModel, getZodSchema), also add Drizzle:

bun add drizzle-orm drizzle-zod

For frontends, use @nilejs/client.

Quick start

1. Write an action

An action is a named schema, the input type inferred from it and a handler that returns Ok or Err.

// src/services/tasks/actions/create.ts
import { createAction } from "@nilejs/nile";
import { Ok } from "@nilejs/nile/fp";
import { z } from "zod";

const createTaskSchema = z.object({
  title: z.string().min(1, "Title is required"),
});

type CreateTaskInput = z.infer<typeof createTaskSchema>;

const createTaskHandler = (input: CreateTaskInput) => {
  const task = {
    id: crypto.randomUUID(),
    title: input.title,
    status: "pending",
  };

  return Ok({ task: task });
};

export const createTaskAction = createAction<CreateTaskInput>({
  name: "create",
  description: "Create a task",
  validation: createTaskSchema,
  handler: createTaskHandler,
});

2. Group actions into a service

// src/services/tasks/index.ts
import { createService } from "@nilejs/nile";
import { createTaskAction } from "./actions/create";

export const tasksService = createService({
  name: "tasks",
  description: "Task management",
  actions: [createTaskAction],
});
// src/services/services.config.ts
import { createServices } from "@nilejs/nile";
import { tasksService } from "./tasks";

export const services = createServices([tasksService]);

3. Start the server

// src/index.ts
import { createNileServer } from "@nilejs/nile";
import { services } from "./services/services.config";

const server = await createNileServer({
  serverName: "my-app",
  runtime: "bun",
  native: true,
  services: services,
  rest: {
    baseUrl: "/api",
    port: 8000,
    allowedOrigins: ["http://localhost:3000"],
  },
});

if (server.nativeRest) {
  Bun.serve({ port: 8000, fetch: server.nativeRest.fetch });
}

Applications that leave out native use the Hono adapter on Bun or Node.js, available as server.rest.app. See runtime adapters.

4. Call it

curl -X POST http://localhost:8000/api/services \
  -H "Content-Type: application/json" \
  -d '{"intent":"execute","service":"tasks","action":"create","payload":{"title":"Ship it"}}'
{
  "status": true,
  "message": "Action completed.",
  "data": {
    "task": { "id": "a1b2c3d4-...", "title": "Ship it", "status": "pending" }
  }
}

From a frontend, @nilejs/client returns the same Result shape as your handlers:

import { createNileClient } from "@nilejs/client";

const nile = createNileClient({ baseUrl: "http://localhost:8000/api" });

const result = await nile.invoke({
  service: "tasks",
  action: "create",
  payload: { title: "Ship it" },
});

if (result.isErr) {
  console.error(result.error);
} else {
  console.log(result.value);
}

Action input and responses

HTTP action calls use a POST to {baseUrl}/services with the same body:

{
  intent: "explore" | "execute" | "schema",
  service: string, // a service name, or "*" for all
  action: string,  // an action name, or "*" for all
  payload: object  // the action's input; use {} when there is none
}
IntentWhat it does
executeValidates the payload against the action's schema and runs the action.
exploreLists services, or the actions in one service.
schemaReturns an action's input as JSON Schema, for clients, tools and AI agents.

Every HTTP action response has exactly these three fields:

{
  status: boolean, // true on success
  message: string, // a readable summary
  data: object     // the action's value, validation details, or {}
}

Validation failures use Zod’s prettified message and include a generated unique data.errorCode alongside data.issues:

{
  "status": false,
  "message": "✖ Title is required\n  → at title",
  "data": {
    "errorCode": "jN8sQ4tV2pLm",
    "issues": [{ "path": "title", "message": "Title is required" }]
  }
}

The code above is illustrative; each failure gets a new reference. Unexpected exceptions are logged internally and return a safe message such as Error Code: jN8sQ4tV2pLm - could not complete the action. Diagnostic codes match the log’s log_id. Custom success messages and intentional Err messages keep their wording.

Properties named raw are removed recursively from outbound JSON and Socket.IO messages, including custom JSON responses. Keep original causes on the backend and choose a different field name for public data. HTTP JSON responses are buffered to apply this filtering.

Discovery (explore and schema) is off unless you enable it with rest.discovery, and can require a secret. See API discovery.

Project structure

The quick start produces this layout:

src/
  index.ts
  services/
    services.config.ts
    tasks/
      index.ts
      actions/
        create.ts

Nile checks the source structure at boot and fails on violations by default. Each service needs an index.ts and an actions/ directory. When an action grows, move it into actions/create/create.ts, export it from actions/create/index.ts, and put private helpers in actions/create/modules/. Action entry points above 400 lines are reported for refactoring.

Set project: { root: ".", structure: "warn" } in the server configuration to report violations and continue, or set structure: false to disable checking. In a monorepo, point root at the backend package. Source-less bundles skip inspection unless a root is explicit. See application structure.

Results and utilities

Import Result helpers and Slang’s utilities through @nilejs/nile/fp; this release includes Slang 0.2.2. Return Results directly instead of nesting them: Ok(Ok(value)) and Ok(Err(message)) are rejected.

Use safeTry for database operations and other promises that can throw. It preserves the original cause on result.raw for internal diagnostics. Nile’s dbTry helper has been removed. Use utils.describeError for bounded diagnostic metadata and utils.handleError for a safe caller message. Nile helpers, including createModel and getZodSchema, are available through the utils namespace:

import { utils } from "@nilejs/nile";
import { safeTry } from "@nilejs/nile/fp";

See shared utilities for examples.

Hooks

A global before hook runs for every action. Use it for shared policy, such as role checks:

import { type BeforeActionHandler, createNileServer } from "@nilejs/nile";
import { Err, Ok, type Result } from "@nilejs/nile/fp";
import { services } from "./services/services.config";

const requireSessionHook: BeforeActionHandler<boolean, string> = (
  hookInput
): Result<boolean, string> => {
  if (!hookInput.action.isProtected) {
    return Ok(true);
  }

  const session = hookInput.nileContext.getSession("rest");
  if (session === undefined) {
    return Err("Sign in to continue");
  }

  return Ok(true);
};

const server = await createNileServer({
  serverName: "my-app",
  services: services,
  onBeforeActionHandler: requireSessionHook,
});

Actions can also list local hooks.before and hooks.after, which reference other registered actions. For an executed action the order is:

JWT check (protected actions)
  -> global before hook
    -> local before hooks
      -> validation
        -> handler
      -> local after hooks
  -> global after hook
-> response

Any failing step stops the rest, except a local hook marked isCritical: false. See hooks and pipelines.

What else is included

FeatureGuide
JWT authentication and access policyAuthentication and hooks
Calls over HTTP, from other actions and from tests with server.invokeCalling actions
Multipart file uploads with limits and type checksFile uploads
Socket.IO actions and typed topicsWebSockets
Rate limiting per action or per clientRate limiting
Cron schedulesCron jobs
Queues, debounce and throttleQueues, debounce and throttle
Retries and shared utilitiesRetries and utilities
Drizzle models with validation and paginationDatabase models
Structured logging and shared resourcesContext and logging
Readiness checks and graceful shutdownReadiness and shutdown
TelemetryTelemetry

Startup statistics

At startup Nile Js prints a table of services with their action counts and registration times. Set logServices: false in createNileServer to turn it off. The same numbers are available in code:

const stats = server.stats();
console.table(stats.services);
// { totalServices, totalActions, registrationDurationMs, startupDurationMs,
//   services: [{ name, description, actionCount, registrationDurationMs }] }

All durations are in milliseconds. startupDurationMs covers server initialization, including onBoot and REST setup, but not module imports. Each call returns a fresh copy, and the numbers do not change after startup.

Schema conversion

Nile Js converts an action's Zod schema to JSON Schema only when a discovery request first needs it, then caches the result. Startup and ordinary execution never convert schemas; validation always runs through Zod.

The cache is keyed by schema object, so later changes to a schema's descriptions or defaults are not picked up. To reconvert on every discovery request, set cacheSchema: false on an action or rest.discovery.cacheSchemas: false for the whole REST app.

Hook execution context

Inside a handler or hook, context.hookContext holds that execution's state, output, errors and logs. Nested action calls get their own context and restore the parent's when they finish. Outside an execution, context.hookContext is a fallback, not the last request's state. To inspect hook logs after a call, set result: { pipeline: true } on the action and read the returned pipeline field.

Using with coding assistants

The package ships SKILL.md, a guide for coding assistants that matches the installed version. After bun install it is at node_modules/@nilejs/nile/SKILL.md. See the Nile Js skill for setup.

Requirements

  • Bun 1.4.2 or later for the native transport and the CLI starter.
  • Node.js 22.23.4+, 24.22.1+ or 26.12.1+ when you run the Hono adapter on Node.js.
  • TypeScript 7 (a peer dependency). Zod 4 and slang-ts install with the package. List Zod in your own dependencies, and import slang-ts helpers from @nilejs/nile/fp so your code uses Nile's copy.

Contributing

Nile Js was first built by Hussein Kizz at Nile Squad Labz to power its own B2B products, and is now open source. Read CONTRIBUTING.md to get started, and join the community to ask questions.

License

MIT

版本与发布记录

@nilejs/nile 共发布 22 个版本,首次发布于 2026/02/28;过去 12 个月发布了 22 个版本。

版本发布日期距今
1.0.4latest2026/11/021 天前
1.0.32026/11/021 天前
1.0.22026/11/021 天前
1.0.12026/10/249 天前
1.0.02026/10/249 天前
0.0.172026/04/206 个月前

关于 @nilejs/nile 的常见问题

@nilejs/nile 是什么?

typescript优先,面向服务和动作的后端框架

如何安装 @nilejs/nile?

在项目目录运行 npm install @nilejs/nile,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:typescript(^7.0.2)、drizzle-orm(^0.45.4)、drizzle-zod(^0.8.3)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@nilejs/nile 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@nilejs/nile 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@nilejs/nile 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js ^22.23.4 || ^24.22.1 || >=26.12.1。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@nilejs/nile 可以商用吗?

@nilejs/nile 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@nilejs/nile 还在维护吗?

最新版本 1.0.4 发布于 2026/11/02(1 天前);至今共发布 22 个版本,其中过去 12 个月发布了 22 个;首次发布于 2026/02/28。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。