Menu
CoddyTech

一个服务

本课属于 Coddy Node.js 学习旅程的“TypeScript 和 NestJS”单元。第 9 课,共 24 课。

控制器自己保存任务,因此应用的其他部分无法访问它们。数据和逻辑应该放在提供者中:Nest 创建这个类,并将它交给需要它的类。

@Injectable()
export class BooksService {
    private books = [{ title: 'Dune' }];
}

@Controller('books')
export class BooksController {
    constructor(private readonly booksService: BooksService) {}
}

@Injectable()用于标记这个类,模块会在providers中列出它。类会在构造函数中写出这个类的名称来请求它:这就是依赖注入。参数前面的private readonly是 TypeScript 的简写:它会创建字段this.booksService,并保存 Nest 传入的内容。Nest 会读取参数的类型。对于带有装饰器的类,编译器会保留这个类型。Nest 会为列出该类的模块创建一个实例,并将同一个实例传给该模块中所有请求它的类。数据会被共享,测试时也可以传入一个模拟对象。

本课一开始,控制器已经在调用this.tasksService。

challenge icon

挑战

简单

这一步会打开课程提供的 Nest 任务跟踪器项目,而不是你在 Status Codes and Errors 中的项目文件(包括 src/tasks/tasks.controller.ts)。你之前的工作会保留在原来的课中。编辑 src/tasks/tasks.service.ts 之前,先阅读提供的文件。这份项目副本包含 Nest 任务路由,控制器已准备好调用服务。

任务保存在控制器中,因此应用的其他部分无法访问它们。把任务移到服务中:服务是一个拥有这些任务的类,Nest 会创建一个实例,并将它传给每个需要它的类。控制器已经改写为调用服务。你要完成服务并连接这两部分。

  1. 在 src/tasks/tasks.service.ts 中,使用来自 @nestjs/common 的 @Injectable() 标记类 TasksService。这样 Nest 就能创建它并提供给其他类。
  2. 在同一个类中,编写方法 create(title)。它会创建一个任务,包含下一个可用的 id(比 this.tasks 中最大的 id 大 1)、该标题以及 done: false。它会把任务添加到 this.tasks 并返回该任务。控制器的 POST /tasks 会使用请求正文中的标题调用它。
  3. 编写方法 remove(id),其中 id 是一个数字。如果没有任务的 id 与它匹配,方法必须像 findOne 一样返回 404:先调用 this.findOne(id),它会抛出异常。然后从 this.tasks 中移除该任务。它不返回任何内容。
  4. 在 src/tasks/tasks.module.ts 中,从 ./tasks.service 导入 TasksService,然后在模块中添加一个 providers 列表,与 controllers 并列,并将 TasksService 放入其中。Nest 只会创建模块在这里列出的类。
  5. 在 src/tasks/tasks.controller.ts 中,将 TasksController 内的注释替换为一个构造函数,该构造函数接收一个参数,写作 private readonly tasksService: TasksService。Nest 会识别类型 TasksService,并传入它创建的服务实例。参数前面的 private readonly 也会创建一个字段 this.tasksService,用于保存传入的值。下面的方法已经使用这个名称。

试试看:向 /tasks 发送 POST 请求,正文为 { "title": "Buy milk" }。你会得到状态码 201 和:

{ "id": 4, "title": "Buy milk", "done": false }

发送 GET /tasks:会得到 4 个任务,Buy milk 排在最后。发送 DELETE /tasks/4:会得到 204。发送 DELETE /tasks/99:会得到 404,消息为 "message": "Task 99 not found"。

亲手试试

import { NotFoundException } from '@nestjs/common';

export interface Task {
    id: number;
    title: string;
    done: boolean;
    createdAt?: string;
}

// 让这个类成为可供 Nest 注入的提供者。
export class TasksService {
    private tasks: Task[] = [
        { id: 1, title: 'Write the README', done: true, createdAt: '2026-01-05T09:00:00.000Z' },
        { id: 2, title: 'Fix the login bug', done: false, createdAt: '2026-01-06T14:30:00.000Z' },
        { id: 3, title: 'Plan the sprint', done: false, createdAt: '2026-01-07T08:15:00.000Z' },
    ];

    findAll(done?: string): Task[] {
        if (done === undefined) {
            return this.tasks;
        }
        return this.tasks.filter((task) => task.done === (done === 'true'));
    }

    findOne(id: number): Task {
        const task = this.tasks.find((task) => task.id === id);
        if (!task) {
            throw new NotFoundException(`Task ${id} not found`);
        }
        return task;
    }

    // 在这里编写 create(title) 和 remove(id)。
}
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

TypeScript 和 NestJS中的所有课

自己练习: Express 在线编辑器