Skip to content

快速开始 ​

本页从一个空目录开始,做出一个经过测试、可以构建和部署的 Cell 后端。开发需要 Node.js ^20.19.0 || >=22.12.0;构建产物可以在 Node.js 18 及以上、Bun 1.2 及以上和 Deno 2 上运行。

创建项目 ​

bash
npx @celljs/cli init backend my-app      # 或 fullstack:React 前端 + 经 RPC 调用的后端
cd my-app
npm install
npx cell dev                             # http://127.0.0.1:3000,保存即重载

cell init 从不提问。不带模板名时列出全部模板;目录非空时需要 --force。生成的项目:

text
my-app/
├── cell.yml                  # 应用配置,用 @Value 读取
├── package.json              # 脚本:dev、build、preview、check、test
├── tsconfig.json             # experimentalDecorators + emitDecoratorMetadata
├── src/node/
│   ├── module.ts             # 后端 DI 模块:导入每个声明了组件的文件
│   └── home-controller.ts
├── test/app.spec.ts          # bun test + createTestApp()
├── AGENTS.md、CLAUDE.md      # 给 coding agent 的指南,与已安装的版本一致
├── .agents/skills/cell/      # 同一份指南,以 agent skill 的形式提供
└── .mcp.json                 # 给支持 MCP 的 agent 启动 `npx cell mcp`

添加接口 ​

新建一个控制器,再在 src/node/module.ts 里导入它的文件。没有被任何模块导入的文件不会被加载,其中的组件也就不存在。

ts
// src/node/todo-controller.ts
import { Body, Controller, Delete, Get, Param, Post } from '@celljs/mvc/lib/node';
import { NotFoundError } from '@celljs/web/lib/node';

interface Todo { id: number; title: string }

@Controller('todos')
export class TodoController {
    protected readonly todos = new Map<number, Todo>();
    protected nextId = 1;

    @Get()
    list(): Todo[] {
        return [...this.todos.values()];
    }

    @Get(':id')
    get(@Param('id') id: number): Todo {
        const todo = this.todos.get(id);
        if (!todo) {
            throw new NotFoundError(`No todo ${id}`);     // 404
        }
        return todo;                                       // 对象以 JSON 返回
    }

    @Post()
    create(@Body('title') title: string): Todo {
        const todo = { id: this.nextId++, title };
        this.todos.set(todo.id, todo);
        return todo;
    }

    @Delete(':id')
    remove(@Param('id') id: number): void {
        this.todos.delete(id);
    }
}
ts
// src/node/module.ts
import './home-controller';
import './todo-controller';
import { autoBind } from '@celljs/core';

export default autoBind();

@celljs/web 是 @celljs/mvc 的依赖;直接从它导入时,也把它加进 package.json(npm install @celljs/web)。

bash
curl -X POST localhost:3000/todos -H 'content-type: application/json' -d '{"title":"write docs"}'
curl localhost:3000/todos/1

状态变多时把它移到服务里,见组件与依赖注入。控制器的装饰器列在 HTTP 接口。

配置 ​

cell.yml 保存应用配置。组件的配置项(server.port 等)写在顶层,你自己的配置写在 app 下;配置默认只在服务端,只有 app.public 和组件标记为 public 的键会进浏览器:

yaml
app:
  greeting: Hello from my-app
  todos:
    max: ${(env.TODOS_MAX ?? 100) * 1}   # 服务端启动时求值;app 下的键没有 schema 类型,* 1 把环境变量的文本转成数字
ts
@Value('app.todos.max')
protected readonly max: number;

npx cell config get app.todos.max --why 给出合并后的值,以及设置过它的每个文件和环境变量。详见项目结构与配置。

检查与测试 ​

bash
npx cell check        # TypeScript、配置、装饰器设置、DI 绑定
bun test              # test/ 下的测试
ts
// test/todo.spec.ts
import { afterAll, beforeAll, describe, expect, it } from 'bun:test';
import { join } from 'node:path';
import { createTestApp, type TestApp } from '@celljs/cli';

describe('todos', () => {
    let app: TestApp;
    beforeAll(async () => { app = await createTestApp({ root: join(__dirname, '..') }); }, 60000);
    afterAll(() => app?.close());

    it('creates a todo', async () => {
        const res = await app.fetch('/todos', { method: 'POST', headers: { 'content-type': 'application/json' }, body: '{"title":"a"}' });
        expect(await res.json()).toEqual({ id: 1, title: 'a' });
    });
});

createTestApp() 在测试进程里启动整个应用,不监听端口。见测试与检查。

构建与运行 ​

bash
npx cell build                 # .cell/output/server/index.mjs:内联了全部依赖的单个文件
npx cell preview               # 按生产环境的方式运行产物
node .cell/output/server/index.mjs

--single 把前端也放进这一个文件,--compile 生成不需要 Node.js 的可执行文件。

部署 ​

bash
npx cell deploy --preset cloudflare --dry-run   # 只给出计划,不上传
npx cell deploy --preset cloudflare

preset 有 node(默认)、cli、cloudflare、fc、scf、lambda 和 vercel。见构建与部署。

下一步 ​

  • 组件目录:数据库、缓存、定时任务、安全、AI 等需求该加哪个 @celljs/* 包。
  • 与 Coding Agent 协作:cell init 生成的 AGENTS.md、skill 和 MCP 工具。