NestJS实战:构建高性能类型安全的Node.js短链接服务

引言

URL是Uniform Resource Locator的缩写,它是分配给网络上独特资源的地址。由于URL的唯一性,没有两个资源可以具有相同的URL。

URL的长度和复杂程度各不相同。URL可能像example.com这样简短,也可能像http://llanfairpwllgwyngyllgogerychwyrndrobwllllantysiliogogogoch.co.uk这样冗长。复杂的URL可能不美观,会导致搜索引擎优化(SEO)问题,并对营销计划产生负面影响。URL缩短器可以将长URL映射为较短的URL,并在使用短URL时将用户重定向到原始URL。

在本教程中,您将使用NestJS创建一个URL缩短器。首先,您将在服务中实现URL缩短和重定向逻辑。然后,您将创建路由处理程序来方便缩短和重定向请求。

先决条件

要按照本教程进行操作,您需要准备以下材料:

  • Node.js 16或更高版本的本地开发环境。请根据您的系统,参考《如何安装Node.js并创建本地开发环境》教程进行设置。
  • 系统上已安装NestJS CLI(您将在第一步中进行设置),并熟悉NestJS。请查阅《NestJS入门指南》。
  • 熟悉TypeScript。您可以查阅《TypeScript编程系列》以了解更多信息。

第一步 – 准备开发环境

在这个步骤中,您将设置所有开始实施URL缩短逻辑所需的内容。您将全局安装NestJS CLI,生成一个新的NestJS应用样板,安装依赖项,并创建项目的模块、服务和控制器。

首先,如果您以前没有安装过Nest CLI,您需要全局安装它。您将使用这个CLI来生成项目目录和所需的文件。运行以下命令以安装Nest CLI:

  1. npm install -g @nestjs/cli

-g选项将全局在您的系统上安装Nest CLI。

您将会看到以下输出结果:

  1. 输出...
  2. added 249 packages, and audited 250 packages in 3m
  3. 39 packages are looking for funding
  4. run npm fund for details
  5. found 0 vulnerabilities

然后,您将使用新安装的命令来创建项目并生成必要的样板引导文件:

  1. nest new URL-shortener

您将会看到以下输出结果:

  1. 输出

这是文章《如何使用NestJS在NodeJS中构建一个类型安全的URL缩短器》的第2部分(共8部分)。

  1. ⚡ 正在为您搭建应用程序...
  2. 创建 url-shortener/.eslintrc.js (631 字节)
  3. 创建 url-shortener/.prettierrc (51 字节)
  4. 创建 url-shortener/nest-cli.json (118 字节)
  5. 创建 url-shortener/package.json (2002 字节)
  6. 创建 url-shortener/README.md (3339 字节)
  7. 创建 url-shortener/tsconfig.build.json (97 字节)
  8. 创建 url-shortener/tsconfig.json (546 字节)
  9. 创建 url-shortener/src/app.controller.spec.ts (617 字节)
  10. 创建 url-shortener/src/app.controller.ts (274 字节)
  11. 创建 url-shortener/src/app.module.ts (249 字节)
  12. 创建 url-shortener/src/app.service.ts (142 字节)
  13. 创建 url-shortener/src/main.ts (208 字节)
  14. 创建 url-shortener/test/app.e2e-spec.ts (630 字节)
  15. 创建 url-shortener/test/jest-e2e.json (183 字节)
  16. ? 您希望使用哪个包管理器? (使用方向键选择)
  17. > npm
  18. yarn
  19. pnpm

选择 npm。

您将看到以下输出结果:

  1. 输出
    √ 安装进行中... ☕
  2. ? 成功创建项目 url-shortener
  3. ? 通过以下命令开始:
  4. $ cd url-shortener
  5. $ npm run start
  6. 感谢您安装 Nest ?
  7. 请考虑向我们的开放集体捐款
  8. 以帮助我们维护此软件包。
  9. ? 捐款:https://opencollective.com/nest

移动到您创建的项目目录中。

  1. cd url-shortener

您将在这个目录下运行所有后续命令。

注意:

当您生成新项目时,NestJS CLI 会创建 app.controller.tsapp.controller.spec.tsapp.service.ts 文件。因为您在本教程中不需要它们,您可以选择删除或忽略它们。

接下来,您将安装所需的依赖项。

本教程需要一些依赖项,您将使用 Node.js 的默认包管理器 npm 进行安装。所需的依赖项包括 TypeORM、SQLite、Class-validator、Class-transformer 和 Nano-ID。

TypeORM是一个对象关系映射(ORM)工具,它极大地简化了TypeScript应用与关系型数据库之间的交互。此ORM与NestJS实现了无缝集成,这得益于NestJS专用的@nestjs/typeorm软件包。您将使用此依赖项以及NestJS原生的typeorm软件包来与SQLite数据库进行交互。

运行以下命令来安装TypeORM及其针对NestJS的专用包:

  1. npm install @nestjs/typeorm typeorm

SQLite是一个轻量级、快速且独立的SQL数据库引擎库。您将使用此依赖项作为数据库,用于存储和检索缩短后的URL。

运行以下命令来安装SQLite:

  1. npm install sqlite3

class-validator软件包包含了在NestJS中用于数据验证的装饰器。您将结合数据传输对象(DTO)使用此依赖项,以验证发送到应用程序的数据。

运行以下命令来安装class-validator

  1. npm install class-validator

class-transformer包允许您将普通对象转换为类的实例,反之亦然。您将与class-validator一起使用此依赖项,因为class-validator无法单独运行。

执行以下命令以安装class-transformer

  1. npm install class-transformer

Nano-ID是一个安全、对URL友好的唯一字符串ID生成器。您将使用此依赖项为每个URL资源生成一个唯一的ID。

运行以下命令来安装Nano-ID:

  1. npm install nanoid@^3.0.0

注意:Nano-ID高于3.0.0版本已停用对CommonJS模块的支持。这个问题可能会导致您的应用程序出错,因为TypeScript编译器生成的JavaScript代码仍使用CommonJS模块系统。

在安装必要的依赖项之后,您将使用Nest CLI生成项目的模块、服务和控制器。模块将组织您的项目结构,服务将处理URL缩短器的所有业务逻辑,而控制器将负责处理路由。

运行以下命令生成您的模块:

  1. nest generate module url

您将看到以下输出结果:

输出
CREATE src/url/url.module.ts (80 bytes) UPDATE src/app.module.ts (304 bytes)

然后运行以下命令来生成您的服务:

  1. nest generate service url --no-spec

使用--no-spec标志可以告诉Nest CLI在生成文件时不包含测试文件。在本教程中,您不需要测试文件。

您将看到下面的输出结果:

输出
CREATE src/url/url.service.ts (87 bytes) UPDATE src/url/url.module.ts (151 bytes)

然后运行以下命令来生成您的控制器:

  1. nest generate controller url --no-spec

您将看到如下输出结果:

输出
CREATE src/url/url.controller.ts (95 bytes) UPDATE src/url/url.module.ts (233 bytes)

在这一步中,您已经为应用程序生成了大部分开发所需的文件。接下来,您将把应用程序连接到一个数据库。

第二步 — 将您的应用程序连接到数据库

在这一步中,您将创建一个实体来模拟数据库中的URL资源。实体是包含存储数据所需属性的文件。您还将创建一个存储库作为应用程序和数据库之间的访问层。

请使用Nano或您偏好的文本编辑器,在src/url文件夹中创建并打开名为url.entity.ts的文件。

  1. nano src/url/url.entity.ts

这个文件将包含模型数据的实体定义。

接下来,在您的src/url/url.entity.ts文件中,添加以下的TypeScript代码:

src/url/url.entity.ts
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';

@Entity()
export class Url {
    @PrimaryGeneratedColumn()
    id: number;

    @Column()
    urlCode: string;

    @Column()
    longUrl: string;

    @Column()
    shortUrl: string;
}

首先,从'typeorm'中导入EntityColumnPrimaryGeneratedColumn装饰器。

代码创建并导出一个带有@Entity()装饰器注释的Url类,该注释将此类标记为TypeORM实体。

每个属性都使用相应的装饰器进行指定和注释:使用@PrimaryGeneratedColumn()装饰器来标识id属性,使用@Column()装饰器标识其他属性。@PrimaryGeneratedColumn()是一个会自动为被注释属性生成值的装饰器。TypeORM将使用它为每个资源生成一个ID。@Column()是一个将被注释属性作为数据库列添加的装饰器。

应该存储在数据库中的属性包括以下内容:

  • id是数据库表的主键。
  • urlCode是由Nano-ID包生成的唯一ID,将用于标识每个URL。
  • longUrl是发送到您的应用程序进行缩短的原始URL。
  • shortUrl是缩短后的URL。

保存并关闭文件。

接下来,您将在应用程序和数据库之间建立连接。

首先,用Nano或者您喜欢的文本编辑器打开src/app.module.ts文件。

  1. nano src/app.module.ts

然后,将这些突出显示的行添加到文件中。

app.module.ts 源代码文件
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Url } from './url/url.entity';
import { UrlModule } from './url/url.module';

@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: 'sqlite',
      database: 'URL.sqlite',
      entities: [Url],
      synchronize: true,
    }),
    UrlModule,
  ],
  controllers: [],
  providers: [],
})
export class AppModule {}

首先,从@nestjs/typeorm导入TypeOrmModule,并从./url/url.entity导入Url实体。您可能仍然保留着与AppControllerAppService相关的代码行,但它们不会影响本教程的其余部分。

imports数组中,调用TypeOrmModuleforRoot方法,以便在应用程序的所有模块中共享数据库连接。forRoot方法接受一个配置对象作为参数。

该配置对象包含用于创建数据库连接的属性,具体如下:

  • type属性:指定您使用TypeORM交互的数据库类型。在此示例中,设置为'sqlite'
  • database属性:指定您数据库的首选名称。在此示例中,设置为'URL.sqlite'
  • entities属性:一个包含项目中所有实体的数组。在此示例中,数组中只指定了一个Url实体。
  • synchronize属性:该选项会在每次运行代码时自动将数据库表与您的实体同步并更新表结构。在此示例中,设置为true

注意:

在开发环境中,将synchronize设置为true是理想的选择。但在生产环境中,它应始终设置为false,以避免潜在的数据丢失。

保存并关闭文件。

接下来,您将创建一个仓库(Repository),作为应用程序与数据库之间的访问层。您需要将实体连接到其父模块,这种连接使得Nest和TypeORM能够自动创建一个仓库。

打开 src/url/url.module.ts

  1. nano src/url/url.module.ts

在现有文件中,添加以下高亮显示的行:

src/url/url.module.ts 的内容如下:

import { Module } from '@nestjs/common';
import { UrlService } from './url.service';
import { UrlController } from './url.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Url } from './url.entity';

@Module({
  imports: [TypeOrmModule.forFeature([Url])],
  providers: [UrlService],
  controllers: [UrlController],
})
export class UrlModule {}

您在Module装饰器内部创建了一个imports数组,其中引入了@nestjs/typeorm中的TypeOrmModule./url.entity中的Url实体。在imports数组内,您调用了TypeOrmModuleforFeature方法。forFeature方法接受一个实体数组作为参数,因此您传入了Url实体。

保存并关闭文件。

Nest和TypeORM将在幕后为您创建一个仓库,作为您的服务与数据库之间的访问层。

在这一步骤中,您已将应用程序连接到数据库。现在,您已准备好实现URL缩短的逻辑。

第三步 — 实现服务逻辑

在这一步中,您将使用两种方法来实现您的服务逻辑。第一种方法是shortenUrl,它将包含所有URL缩短逻辑。第二种方法是redirect,它将包含将用户重定向到原始URL的所有逻辑。您还将创建一个数据传输对象(DTO)来验证进入应用程序的数据。

为服务提供对仓库的访问

在实现这些方法之前,您将授权您的服务访问您的仓库,以便您的应用程序能够读取和写入数据库中的数据。

首先,打开src/url/url.service.ts

  1. nano src/url/url.service.ts

请将以下高亮显示的行添加到现有文件中:

src/url/url.service.ts 的内容如下:

import { Injectable } from '@nestjs/common';
import { Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { Url } from './url.entity';

@Injectable()
export class UrlService {
  constructor(
    @InjectRepository(Url)
    private repo: Repository<Url>,
  ) {}
}

您从typeorm中导入Repository,从@nestjs/typeorm中导入InjectRepository,以及从./url.entity中导入Url实体。

在您的UrlService类中,您创建了一个构造函数。在构造函数内部,您声明了一个私有变量repo作为参数。然后,您将repo的类型设置为具有Url泛型类型的Repository。您使用@InjectRepository装饰器注解了repo变量,并将Url作为参数传递进去。

保存并关闭文件。

您的服务现在可以通过repo变量访问您的仓库。所有的数据库查询和TypeORM方法都将在此变量上调用。

接下来,您将创建一个名为shortenUrl的异步方法。该方法将接受一个URL作为参数并返回一个缩短的URL。为了验证传入方法的数据是否有效,您将使用数据传输对象(DTO)以及class-validatorclass-transformer包来验证数据。

创建一个数据传输对象(DTO)

在创建异步方法之前,您将创建shortenUrl异步方法所需的数据传输对象(DTO)。数据传输对象是定义数据如何在应用程序之间传输的对象。

首先,在您的URL文件夹中创建一个dtos(数据传输对象)文件夹。

  1. mkdir src/url/dtos

然后,在该文件夹中创建一个名为url.dto.ts的文件。

  1. nano src/url/dtos/url.dto.ts

将以下代码添加到新文件中:

src/url/dtos/url.dto.ts 的内容如下:

import { IsString, IsNotEmpty } from 'class-validator';

export class ShortenURLDto {
  @IsString()
  @IsNotEmpty()
  longUrl: string;
}

您从class-validator中导入IsStringIsNotEmpty装饰器。然后,您创建并导出了ShortenURLDto类。在ShortenURLDto类内部,您创建了一个longUrl属性,并赋予了string类型。

您还使用@IsString@IsNotEmpty装饰器对longUrl属性进行了注解。使用这些装饰器注解longUrl属性将确保longUrl始终为字符串且不为空。

保存并关闭文件。

然后,打开您的src/main.ts文件。

  1. nano src/main.ts

将下面的代码段添加到现有文件中:

src/main.ts 的内容如下:

这是文章《如何使用NestJS在NodeJS中构建一个类型安全的URL缩短器》的第5部分(共8部分)。

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(new ValidationPipe({ whitelist: true }));
  await app.listen(3000);
}
bootstrap();

您导入了ValidationPipe,该管道使用class-validator包来对输入您应用程序的所有数据进行验证。

然后,您在应用实例(app)上调用useGlobalPipes方法,并传递一个ValidationPipe实例以及一个选项对象。选项对象中的whitelist属性被设置为trueuseGlobalPipes方法在应用级别绑定ValidationPipe,确保所有的路由都受到保护,以防止不正确的数据。将whitelist属性设置为true会剥离已验证(返回的)对象中未在DTO中指定的属性。

保存并关闭文件。

接下来,您将把数据传输对象导入到url.service.ts文件中,并应用到shortenUrl方法中。

创建短链接方法

缩短网址方法将处理大部分的网址缩短逻辑。该方法将接受一个类型为ShortenURLDto的参数url

首先,打开您的src/url/url.service.ts文件。

  1. nano src/url/url.service.ts

将下面的行添加到文件中的突出显示的部分:

src/url/url.service.ts 的内容如下:

import {
  BadRequestException,
  Injectable,
  NotFoundException,
  UnprocessableEntityException,
} from '@nestjs/common';
import { Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { Url } from './url.entity';
import { ShortenURLDto } from './dtos/url.dto';
import { nanoid } from 'nanoid';
import { isURL } from 'class-validator';
...

首先,您从 @nestjs/common 中导入 NotFoundExceptionBadRequestExceptionUnprocessableEntityException,因为您将在错误处理中使用它们。接下来,您从 nanoid 中导入 nanoid,并从 class-validator 中导入 isURLisURL 将用于确认提供的 longUrl 是否为有效的 URL。最后,您从 ./dtos/url.dto 导入 ShortenURLDto 用于数据验证。

然后,在下面的UrlService类构造函数中添加以下内容:

src/url/url.service.ts 的中文翻译如下:

...
async shortenUrl(url: ShortenURLDto) {}

然后,将下面的代码添加到您的 shortenUrl 方法中。

src/url/url.service.ts

...
    const { longUrl } = url;

    // 检查长链接是否为有效URL
    if (!isURL(longUrl)) {
      throw new BadRequestException('字符串必须是有效的URL');
    }

    const urlCode = nanoid(10);
    const baseURL = 'http://localhost:3000';

    try {
      // 检查URL是否已被缩短
      let url = await this.repo.findOneBy({ longUrl });
      // 如果存在,则直接返回其短链接
      if (url) return url.shortUrl;

      // 如果不存在,则进行缩短
      const shortUrl = `${baseURL}/${urlCode}`;

      // 将新记录添加到数据库
      url = this.repo.create({
        urlCode,
        longUrl,
        shortUrl,
      });

      this.repo.save(url);
      return url.shortUrl;
    } catch (error) {
      console.log(error);
      throw new UnprocessableEntityException('服务器错误');
    }

在上述代码块中,长链接(longUrl)从url对象中解构出来。随后,使用isURL方法对longUrl进行有效性检查和验证。

接着,使用nanoid生成一个urlCode。默认情况下,nanoid会生成一个由21个字符组成的唯一字符串。若要更改此默认行为,可将所需长度作为参数传入。在此示例中,我们传入了10,以使URL尽可能短。

然后,定义了基础URL(baseURL)。基础URL是您网站地址的统一根目录。在开发环境中,它通常是您的本地主机服务器;在生产环境中,则是您的域名。本教程的示例代码使用了localhost

一个try-catch块将用于处理所有与数据库相关的错误。

为避免重复数据(因为同一个URL可能会被缩短多次),代码会先在数据库中执行查找查询,判断该URL是否已存在。如果存在,则直接返回其对应的短链接;否则,代码将继续执行缩短操作。如果在数据库中未找到该URL记录,则通过拼接基础URL和URL代码来创建一个新的短链接。

随后,使用urlCodelongUrlshortUrl创建一个URL实体实例。调用reposave方法并将此实例作为参数传入,即可将URL实例保存到数据库中。最后,返回shortUrl

最终,如果发生错误,错误信息将在catch块中记录到控制台,并抛出一个UnprocessableEntityException异常。

至此,您的url.service.ts文件内容如下所示:

src/url/url.service.ts的中文原生释义:
import {
  BadRequestException,
  Injectable,
  NotFoundException,
  UnprocessableEntityException,
} from '@nestjs/common';
import { Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { Url } from './url.entity';
import { ShortenURLDto } from './dtos/url.dto';
import { nanoid } from 'nanoid';
import { isURL } from 'class-validator';

@Injectable()
export class UrlService {
  constructor(
    @InjectRepository(Url)
    private repo: Repository<Url>,
  ) {}

  async shortenUrl(url: ShortenURLDto) {
    const { longUrl } = url;

    // 检查长链接是否为有效URL
    if (!isURL(longUrl)) {
      throw new BadRequestException('字符串必须是有效的URL');
    }

    const urlCode = nanoid(10);
    const baseURL = 'http://localhost:3000';

    try {
      // 检查URL是否已被缩短
      let url = await this.repo.findOneBy({ longUrl });
      // 如果存在,则直接返回其短链接
      if (url) return url.shortUrl;

      // 如果不存在,则进行缩短
      const shortUrl = `${baseURL}/${urlCode}`;

      // 将新记录添加到数据库
      url = this.repo.create({
        urlCode,
        longUrl,
        shortUrl,
      });

      this.repo.save(url);
      return url.shortUrl;
    } catch (error) {
      console.log(error);
      throw new UnprocessableEntityException('服务器错误');
    }
  }
}

保存文件。

至此,您已设置了URL缩短逻辑的第一部分。接下来,您将在服务中实现重定向方法。

创建重定向方法

重定向方法将包含将用户重定向到长URL的逻辑。

src/url/url.service.ts文件中,继续在UrlService类的底部添加以下代码,以实现重定向方法:

src/url/url.service.ts
...
  async redirect(urlCode: string) {
    try {
      const url = await this.repo.findOneBy({ urlCode });
      if (url) return url;
    } catch (error) {
      console.log(error);
      throw new NotFoundException('资源未找到');
    }
  }

重定向方法接受urlCode作为参数,并尝试在数据库中查找具有匹配urlCode的资源。如果资源存在,则返回该资源;否则,将抛出一个NotFoundException错误。

您最终完成的url.service.ts文件现在看起来是这样的:

src/url/url.service.ts这个文件是URL服务的实现。

这是文章《如何使用NestJS在NodeJS中构建一个类型安全的URL缩短器》的第7部分(共8部分)。

import {
BadRequestException,
Injectable,
NotFoundException,
UnprocessableEntityException,
} from '@nestjs/common';
import { Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { Url } from './url.entity';
import { ShortenURLDto } from './dtos/url.dto';
import { nanoid } from 'nanoid';
import { isURL } from 'class-validator';

@Injectable()
export class UrlService {
constructor(
@InjectRepository(Url)
private repo: Repository<Url>,
) {}

async shortenUrl(url: ShortenURLDto) {
const { longUrl } = url;

// 检查 longUrl 是否为有效 URL
if (!isURL(longUrl)) {
throw new BadRequestException('字符串必须是有效的URL');
}

const urlCode = nanoid(10);
const baseURL = 'http://localhost:3000';

try {
// 检查 URL 是否已被缩短
let url = await this.repo.findOneBy({ longUrl });
// 如果存在,则直接返回
if (url) return url.shortUrl;

// 如果不存在,则进行缩短
const shortUrl = `${baseURL}/${urlCode}`;

// 将新记录添加到数据库
url = this.repo.create({
urlCode,
longUrl,
shortUrl,
});

this.repo.save(url);
return url.shortUrl;
} catch (error) {
console.log(error);
throw new UnprocessableEntityException('服务器错误');
}
}

async redirect(urlCode: string) {
try {
const url = await this.repo.findOneBy({ urlCode });
if (url) return url;
} catch (error) {
console.log(error);
throw new NotFoundException('资源未找到');
}
}
}

保存并关闭文件。

至此,您的URL缩短逻辑已完成,它包含两个核心方法:一个用于URL缩短,另一个用于将缩短的URL重定向到原始URL。

接下来,您将在控制器类中实现这两种方法的路由处理程序。

第四步 – 实施控制器逻辑

在本步骤中,您将创建两个路由处理程序:一个POST路由处理程序用于处理缩短请求,一个GET路由处理程序用于处理重定向请求。

在控制器类中实现路由之前,您必须将服务注入到控制器中。

首先,打开您的 src/url/url.controller.ts 文件。

  1. nano src/url/url.controller.ts

将以下高亮行添加到文件中:

src/url/url.controller.ts 文件内容:

import { Controller } from '@nestjs/common';
import { UrlService } from './url.service';

@Controller('url')
export class UrlController {
constructor(private service: UrlService) {}
}

首先,您从 ./url.service 导入 UrlService。然后,在您的控制器类中,您声明一个构造函数并初始化一个私有变量 service 作为参数,并将其类型指定为 UrlService

@Controller 装饰器当前有一个名为 'url' 的字符串作为参数,这意味着该控制器只能处理发送到 localhost:3000/url/ 路由的请求。由于这种行为,URL缩短逻辑中出现了一个错误:缩短的URL包含一个基本URL(localhost:3000)和一个URL代码(例如 wyt4_uyP-Il),它们组合起来形成了新的URL(例如 localhost:3000/wyt4_uyP-Il)。因此,该控制器无法处理这些请求,缩短链接将返回 404 Not Found 错误。为了解决这个问题,请从 @Controller 装饰器中移除 'url' 参数,并为每个处理程序实现各自的路由。

在您移除 'url' 参数后,您的 UrlController 将变为:

src/url/url.controller.ts 文件内容:

@Controller()
export class UrlController {
constructor(private service: UrlService) {}
}

仍在 src/url/url.controller.ts 文件中,将高亮项添加到导入语句中:

src/url/url.controller.ts 文件内容:

import { Body, Controller, Get, Param, Post, Res } from '@nestjs/common';
import { UrlService } from './url.service';
import { ShortenURLDto } from './dtos/url.dto';

您从 @nestjs/common 中导入了 BodyGetParamPostRes,以及从 ./dtos/url.dto 中导入了 ShortenURLDto。当您在此文件中添加时,这些装饰器将进一步定义。

然后,在构造函数下方将以下代码添加到 UrlController 中以定义POST路由处理程序:

src/url/url.controller.ts 文件内容:

...
@Post('shorten')
shortenUrl(
@Body()
url: ShortenURLDto,
) {
return this.service.shortenUrl(url);
}

您创建了一个名为 shortenUrl 的方法,它带有一个类型为 ShortenURLDtourl 参数。通过使用 @Body 装饰器对 url 进行注释,可以从请求对象中提取请求体并将其值填充到 url 变量中。

然后,您使用 @Post 装饰器对整个方法进行注解,并将 'shorten' 作为参数传递。该处理程序将处理发送到 localhost:<端口>/shorten 的所有POST请求。接着,您调用服务上的 shortenUrl 方法,并将 url 作为参数传递。

接下来,在POST路由下方添加以下代码来定义用于重定向的GET路由处理程序:

src/url/url.controller.ts 文件内容:

...
@Get(':code')
async redirect(
@Res() res,
@Param('code')
code: string,
) {
const url = await this.service.redirect(code);

return res.redirect(url.longUrl);
}

您创建了一个带有两个参数的 redirect 方法:一个被 @Res 装饰器注释的 res,一个被 @Param 装饰器注释的 code@Res 装饰器将其注释的类转换为Express响应对象,允许您使用特定库的命令,例如Express的重定向方法。@Param 装饰器从请求对象中提取参数属性,并将其值填充到被装饰的参数中。

您使用 @Get 装饰器对 redirect 方法进行注释,并传递一个通配符参数 ':code'。然后您将 'code' 作为参数传递给 @Param 装饰器。

接着,您在服务上调用 redirect 方法,等待结果,并将其存储在变量 url 中。

最后,您返回 res.redirect() 并传递 url.longUrl 作为参数。这个方法将处理发送到 localhost:<端口>/code 的GET请求,也就是您的缩短网址。

您的 src/url/url.controller.ts 文件现在将看起来像这样:

这是文章《如何使用NestJS在NodeJS中构建一个类型安全的URL缩短器》的第8部分(共8部分)。

import { Body, Controller, Get, Param, Post, Res } from '@nestjs/common';
import { UrlService } from './url.service';
import { ShortenURLDto } from './dtos/url.dto';

@Controller()
export class UrlController {
  constructor(private service: UrlService) {}

  @Post('shorten')
  shortenUrl(
    @Body()
    url: ShortenURLDto,
  ) {
    return this.service.shortenUrl(url);
  }

  @Get(':code')
  async redirect(
    @Res() res,
    @Param('code')
    code: string,
  ) {
    const url = await this.service.redirect(code);

    return res.redirect(url.longUrl);
  }
}

保存并关闭文件。

现在,您已经定义了您的POST和GET路由处理程序,您的URL缩短器功能已经完全可用。在下一步中,您将对其进行测试。

步骤五 — 测试URL缩短服务

在这个步骤中,您将测试在之前步骤中定义的URL缩短工具。

首先,通过运行程序来启动您的应用。

  1. npm run start

您将会看到如下输出:

Output
[Nest] 12640 - 06/08/2022, 16:20:04 LOG [NestFactory] Starting Nest application... [Nest] 12640 - 06/08/2022, 16:20:07 LOG [InstanceLoader] AppModule dependencies initialized +2942ms [Nest] 12640 - 06/08/2022, 16:20:07 LOG [InstanceLoader] TypeOrmModule dependencies initialized +1ms [Nest] 12640 - 06/08/2022, 16:20:08 LOG [InstanceLoader] TypeOrmCoreModule dependencies initialized +257ms [Nest] 12640 - 06/08/2022, 16:20:08 LOG [InstanceLoader] TypeOrmModule dependencies initialized +2ms [Nest] 12640 - 06/08/2022, 16:20:08 LOG [InstanceLoader] UrlModule dependencies initialized +4ms [Nest] 12640 - 06/08/2022, 16:20:08 LOG [RoutesResolver] UrlController {/}: +68ms [Nest] 12640 - 06/08/2022, 16:20:08 LOG [RouterExplorer] Mapped {/shorten, POST} route +7ms [Nest] 12640 - 06/08/2022, 16:20:08 LOG [RouterExplorer] Mapped {/:code, GET} route +2ms [Nest] 12640 - 06/08/2022, 16:20:08 LOG [NestApplication] Nest application successfully started +7ms

打开一个新的终端,使用curl或您喜欢的API测试工具,向http://localhost:3000/shorten发出POST请求,使用下面的数据或任意您选择的数据。有关使用curl的更多信息,请参阅本教程中的描述。

运行这个命令进行一个示例的POST请求。

  1. curl -d "{"longUrl":"http://llanfairpwllgwyngyllgogerychwyrndrobwllllantysiliogogogoch.co.uk"}" -H "Content-Type: application/json" http://localhost:3000/shorten

使用-d标志注册HTTP POST请求数据,并使用-H标志设置HTTP请求的头部。运行此命令将向您的应用程序发送长网址并返回一个缩短的URL。

您将收到一个短链接作为回复,例如下面这个例子:

http://localhost:3000/MWBNHDiloW

最后,复制短网址并将链接粘贴到浏览器中,然后按下回车键。您将被重定向到原始资源。

结论

在本文中,您使用NestJS创建了一个URL缩短工具。如果您添加一个前端界面,就可以将其部署供公众使用。您可以在Github上查看完整的项目。

NestJS 提供了类型安全和架构,使您的应用程序更加安全、可维护和可扩展。想要了解更多信息,请访问 NestJS 的官方文档。

bannerAds