NestJS + TypeScript 全栈项目骨架实战

3 阅读13分钟

对前端转全栈来说,NestJS + TypeScript 是「零语言切换成本、快速落地」的最优解。这一章,我们不聊理论,只做「手把手实操」—— 从环境准备到项目骨架搭建,再到第一个接口开发,全程带代码、带命令,让你 1 小时内跑通全栈项目基础架构。

核心目标:搭建一个「前端可调用、支持 AI API 对接、带数据库连接」的全栈后端骨架,为后续 AI 功能落地打基础。

一、前置环境准备(必做,5 分钟搞定)

NestJS 基于 Node.js,所以先确保你的环境满足要求,按以下步骤操作:

1. 安装 Node.js(核心依赖)

  • 要求:Node.js 版本 ≥ 18.x(推荐 18.17.0 或 20.x,LTS 版本更稳定)
  • 下载地址:Node.js官网(选对应系统的 LTS 版本)
  • 验证:安装完成后,打开终端输入以下命令,能显示版本号即成功:
node -v # 输出 v18.17.0 之类的版本号
npm -v  # 输出 9.x 或 10.x 版本号

2. 安装 Nest CLI(项目脚手架,必装)

Nest CLI 能快速创建项目、生成模块 / 控制器 / 服务,前端同学可以理解为「Nest 版的 Vue CLI/Create React App」。

终端执行以下命令全局安装:

npm install -g @nestjs/cli

验证:输入 nest -v,显示版本号即成功(如 10.3.0)。

3. 可选工具(提升开发效率)

  • 代码编辑器:推荐 VS Code,安装以下插件:
    • ESLint(代码校验)
    • Prettier(代码格式化)
    • NestJS Snippets(Nest 语法提示)
    • Prisma(若选 Prisma 数据库,提前安装)
    • TypeORM(若选 TypeORM 数据库,提前安装)
  • 终端:Windows 推荐 PowerShell/Windows Terminal,Mac/Linux 用自带终端即可。
  • API 调试工具:Postman 或 Apifox(后续测试接口用)。

二、创建 NestJS 项目(10 分钟搞定)

1. 初始化项目

终端进入你想存放项目的文件夹(如 ~/projects),执行以下命令创建项目:

# nest new 项目名(推荐用英文,比如 ai-fullstack-demo)
nest new ai-fullstack-demo

执行后会出现选项:

  • 选择包管理器:推荐选 npm(最通用,避免后续依赖问题)
  • 等待安装依赖(约 1-3 分钟,取决于网络)

2. 项目目录结构解析(前端视角看懂核心目录)

安装完成后,用 VS Code 打开项目,核心目录结构如下(不用记,先有个印象):

ai-fullstack-demo/
├── src/                  # 核心代码目录(所有业务逻辑写这里)
│   ├── app.controller.ts # 控制器(处理路由、接收请求)→ 类似前端的路由配置
│   ├── app.service.ts    # 服务(处理业务逻辑)→ 类似前端的工具函数/API 封装
│   ├── app.module.ts     # 根模块(项目入口,整合所有功能模块)→ 类似前端的入口文件
│   └── main.ts           # 项目启动文件(配置端口、中间件等)
├── package.json          # 依赖配置(和前端一样)
├── tsconfig.json         # TypeScript 配置(前端同学熟悉的配置文件)
└── nest-cli.json         # Nest CLI 配置(无需修改,默认即可)

对前端同学的通俗解释:

  • 控制器(Controller):负责「接收请求」—— 比如前端调用 /api/ai/generate,就由对应的控制器处理路由;
  • 服务(Service):负责「处理逻辑」—— 比如调用 OpenAI API、操作数据库,都写在 Service 里;
  • 模块(Module):负责「整合功能」—— 比如把 AI 相关的控制器、服务、数据库模型打包成一个 AiModule,结构清晰。

3. 启动项目,验证环境

终端进入项目根目录,执行启动命令:

cd ai-fullstack-demo
npm run start:dev # 开发模式启动(热更新,改代码不用重启服务)

启动成功后,终端会显示:

[Nest] 12345  - 2026/04/07 10:00:00     LOG [NestFactory] Starting Nest application...
[Nest] 12345  - 2026/04/07 10:00:01     LOG [InstanceLoader] AppModule dependencies initialized +100ms
[Nest] 12345  - 2026/04/07 10:00:01     LOG [NestApplication] Nest application successfully started +50ms

打开浏览器访问 http://localhost:3000,能看到 Hello World! 即说明项目启动成功!

三、搭建核心模块(全栈骨架核心,30 分钟搞定)

我们要搭建「用户模块 + AI 模块 + 数据库连接」的基础骨架,后续所有功能(如 AI 生成代码、用户登录)都基于这个结构扩展。

1. 用 Nest CLI 快速生成模块(高效不手写)

Nest CLI 支持自动生成模块、控制器、服务,避免手动创建文件和配置,终端执行以下命令:

# 生成用户模块(处理用户登录、注册等)
nest generate module modules/user
nest generate controller modules/user # 生成用户控制器
nest generate service modules/user   # 生成用户服务
# 生成 AI 模块(处理 AI API 调用、生成功能等)
nest generate module modules/ai
nest generate controller modules/ai
nest generate service modules/ai

执行后,项目会新增 src/modules 目录,自动创建 user 和 ai 两个模块,且会自动在根模块 app.module.ts 中导入(不用手动配置,太香了!)。

2. 配置 TypeScript(前端友好,统一类型规范)

打开 tsconfig.json,确保以下配置(默认已配置,重点看这几项):

{
  "compilerOptions": {
    "module": "commonjs",
    "declaration": true,
    "removeComments": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true, // 启用装饰器(Nest 核心特性)
    "target": "ES2021", // 目标 ES 版本,兼容 Node.js 18+
    "sourceMap": true,
    "outDir": "./dist",
    "baseUrl": "./",
    "incremental": true,
    "skipLibCheck": true,
    "strictNullChecks": true, // 严格 null 检查(避免 undefined 报错,前端熟悉)
    "noImplicitAny": true, // 禁止隐式 any 类型(强制写类型,更规范)
    "strictBindCallApply": true,
    "forceConsistentCasingInFileNames": true
  }
}

这些配置和前端项目的 TS 配置基本一致,前端同学不用额外学习。

3. 数据库选型与连接(TypeORM vs Prisma 二选一,前端友好)

全栈项目离不开数据库,NestJS 最常用的两种 ORM 工具是 TypeORMPrisma。两者各有优势,前端同学可根据自身情况选择,以下先对比核心差异,再分别给出实操步骤:

3.1 TypeORM vs Prisma 核心对比(前端视角)
对比维度TypeORMPrisma前端转全栈适配度
核心定位传统 ORM(对象关系映射)下一代 ORM(类型安全查询构建器)两者均高,Prisma 更易上手
类型安全依赖 TypeScript 装饰器,需手动定义类型自动生成类型,零手动维护Prisma 更优(前端熟悉的 “自动类型” 逻辑)
模型定义用「实体类 + 装饰器」映射数据库表用「Prisma Schema DSL」定义模型TypeORM 更贴近前端 “类 + 装饰器” 思维;Prisma 更简洁
学习成本中(需学装饰器、Repository、查询构建器)低(语法简洁,类似写 interface)Prisma 更低(前端无额外认知负担)
开发效率中等(查询需拼接 Repository 方法)高(链式查询 + 自动补全,少写冗余代码)Prisma 更优
生态适配NestJS 官方推荐,支持所有数据库NestJS 无缝集成,支持主流数据库持平
迁移体验命令行生成迁移文件,需手动调整 SQL声明式迁移,自动生成 SQL,支持回滚Prisma 更友好(前端不用懂复杂 SQL)
调试体验需打印 SQL 调试,类型错误运行时才暴露编译时类型校验,Prisma Studio 可视化调试Prisma 更优

选型建议

  • 若你 已用 TypeORM 或熟悉类 + 装饰器语法(比如 React 装饰器),选 TypeORM,无缝衔接前端思维;
  • 若你 刚起步、怕麻烦、想少写代码,选 Prisma,自动类型提示 + 可视化工具,开发效率拉满。
3.2 方案一:TypeORM + SQLite(适合已有 TypeORM 经验的同学)

SQLite 是文件型数据库(不用安装服务,零配置,适合开发阶段),TypeORM 是 NestJS 官方推荐 ORM,以下是完整实操:

步骤 1:安装 TypeORM 依赖
npm install @nestjs/typeorm typeorm sqlite3
步骤 2:定义 TypeORM 实体(数据库表结构)

新建 src/modules/user/entities/user.entity.ts:

import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, OneToMany } from 'typeorm';
import { AiRecord } from '../../ai/entities/ai-record.entity';
@Entity('users') // 映射数据库表 users
export class User {
  @PrimaryGeneratedColumn('uuid') // 主键,UUID 类型
  id: string;
  @Column({ unique: true, length: 64 }) // 唯一字段,字符串类型
  username: string;
  @Column({ unique: true, length: 128 })
  email: string;
  @Column({ length: 255 })
  password: string;
  @CreateDateColumn({ name: 'created_at' }) // 自动维护创建时间
  createdAt: Date;
  @UpdateDateColumn({ name: 'updated_at' }) // 自动维护更新时间
  updatedAt: Date;
  @OneToMany(() => AiRecord, (aiRecord) => aiRecord.user) // 一对多关联(关联 AI 生成记录)
  aiRecords: AiRecord[];
}

新建 src/modules/ai/entities/ai-record.entity.ts:

import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, ManyToOne, JoinColumn } from 'typeorm';
import { User } from '../../user/entities/user.entity';
@Entity('ai_records') // 映射数据库表 ai_records
export class AiRecord {
  @PrimaryGeneratedColumn('uuid')
  id: string;
  @Column('text') // 长文本字段(存储 AI 生成内容)
  content: string;
  @Column({ length: 32 }) // 生成类型(如 "code"、"text")
  type: string;
  @Column({ length: 32, default: 'success' }) // 状态(success/error)
  status: string;
  @Column({ name: 'user_id' }) // 外键字段(关联用户表)
  userId: string;
  @CreateDateColumn({ name: 'created_at' })
  createdAt: Date;
  @UpdateDateColumn({ name: 'updated_at' })
  updatedAt: Date;
  @ManyToOne(() => User, (user) => user.aiRecords) // 多对一关联
  @JoinColumn({ name: 'user_id' }) // 显式指定外键列名,避免歧义
  user: User;
}
步骤 3:配置 TypeORM 数据源

新建 src/config/typeorm.config.ts:

import { DataSource } from 'typeorm';
import { ConfigService } from '@nestjs/config';
import { User } from '../modules/user/entities/user.entity';
import { AiRecord } from '../modules/ai/entities/ai-record.entity';
export const getTypeOrmConfig = (configService: ConfigService) => ({
  type: 'sqlite', // 数据库类型:SQLite
  database: configService.get('DATABASE_URL') || 'dev.db', // 数据库文件(自动生成)
  entities: [User, AiRecord], // 注册实体(数据库表映射)
  synchronize: false, // 生产环境禁用!用迁移管理表结构
  migrations: ['dist/src/migrations/*.js'], // 迁移文件路径
  migrationsTableName: 'migrations', // 迁移记录表名
});

在根模块 src/app.module.ts 中导入 TypeORM 配置:

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UserModule } from './modules/user/user.module';
import { AiModule } from './modules/ai/ai.module';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { getTypeOrmConfig } from './config/typeorm.config';
@Module({
  imports: [
    ConfigModule.forRoot({ isGlobal: true }), // 加载环境变量(.env 文件)
    TypeOrmModule.forRootAsync({
      useFactory: getTypeOrmConfig,
      inject: [ConfigService], // 注入配置服务
    }),
    UserModule,
    AiModule,
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}
步骤 4:生成数据库表(迁移命令)
# 1. 生成迁移文件(基于实体类变更)
npx typeorm-ts-node-commonjs migration:generate src/migrations/init-tables --dataSource src/config/typeorm.config.ts
# 2. 执行迁移(创建数据库表)
npx typeorm-ts-node-commonjs migration:run --dataSource src/config/typeorm.config.ts

执行成功后,项目根目录会生成 dev.db 数据库文件,表结构与实体类一致。

3.3 方案二:Prisma + SQLite(适合新手、追求高效的同学)

Prisma 是 TypeScript 友好的 ORM 工具,自动生成类型,不用懂 SQL,以下是完整实操:

步骤 1:安装 Prisma 依赖
npm install prisma --save-dev
npm install @prisma/client
步骤 2:初始化 Prisma
npx prisma init

执行后会生成:

  • prisma/schema.prisma:数据库模型配置文件(定义表结构);
  • .env:环境变量文件(默认生成 DATABASE_URL,配置数据库连接地址)。
步骤 3:配置数据库连接(SQLite)

打开 .env 文件,修改 DATABASE_URL 为 SQLite 连接地址:

# 原配置(PostgreSQL)注释掉,替换为以下内容
DATABASE_URL="file:./dev.db" # SQLite 数据库文件(会自动生成在 prisma 目录下)
步骤 4:定义 Prisma 模型(数据库表结构)

打开 prisma/schema.prisma,替换为以下代码:

generator client {
  provider = "prisma-client-js"
}
datasource db {
  provider = "sqlite" // 数据库类型:SQLite
  url      = env("DATABASE_URL") // 连接地址(从 .env 读取)
}
// 用户表(存储用户信息,后续登录用)
model User {
  id        String      @id @default(uuid()) // 主键,自动生成 UUID
  username  String      @unique // 用户名(唯一)
  email     String      @unique // 邮箱(唯一)
  password  String      // 密码(后续会加密)
  createdAt DateTime    @default(now()) // 创建时间
  updatedAt DateTime    @updatedAt // 更新时间
  aiRecords AiRecord[]  // 关联 AI 生成记录(一对多)
}
// AI 生成记录表(存储 AI 生成的内容,如代码、文案)
model AiRecord {
  id        String   @id @default(uuid())
  content   String   // 生成的内容(如代码字符串)
  type      String   // 生成类型(如 "code"、"text")
  status    String   @default("success") // 状态(success/error)
  userId    String   // 关联的用户 ID
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
  user      User     @relation(fields: [userId], references: [id]) // 关联用户表
}
步骤 5:生成数据库和 Prisma 客户端
npx prisma migrate dev --name init
  • --name init:给这次数据库迁移起个名字(初始化);
  • 执行成功后,会生成 prisma/dev.db 数据库文件,且自动生成 TypeScript 客户端(用于操作数据库)。
步骤 6:封装 Prisma 全局服务
nest generate service prisma

打开 src/prisma/prisma.service.ts,替换为以下代码:

import { Injectable, OnModuleInit } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit {
  // 模块初始化时连接数据库
  async onModuleInit() {
    await this.$connect();
  }
}

打开 src/prisma/prisma.module.ts,修改为全局模块:

import { Global, Module } from '@nestjs/common';
import { PrismaService } from './prisma.service';
@Global() // 标记为全局模块,所有模块无需导入即可使用
@Module({
  providers: [PrismaService],
  exports: [PrismaService], // 导出服务,供其他模块使用
})
export class PrismaModule {}

在根模块 src/app.module.ts 中导入 PrismaModule:

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UserModule } from './modules/user/user.module';
import { AiModule } from './modules/ai/ai.module';
import { PrismaModule } from './prisma/prisma.module'; // 导入 Prisma 模块
@Module({
  imports: [PrismaModule, UserModule, AiModule], // 加入全局模块
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

四、开发第一个接口(验证骨架可用性,二选一)

根据你选择的数据库方案,以下分别给出 TypeORM 和 Prisma 版本的 AI 生成接口,验证模块、数据库、路由是否正常工作:

方案一:TypeORM 版本接口

步骤 1:定义请求参数 DTO

新建 src/modules/ai/dto/generate-text.dto.ts:

// 定义 AI 生成请求的参数类型(前端可复用)
export class GenerateTextDto {
  prompt: string; // 提示词(如 "写一段前端学习文案")
  type: string; // 生成类型(如 "text")
  userId: string; // 关联的用户 ID(测试用,后续替换为登录用户)
}
步骤 2:编写 AI 服务逻辑

打开 src/modules/ai/ai.service.ts,替换为以下代码:

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { AiRecord } from './entities/ai-record.entity';
import { GenerateTextDto } from './dto/generate-text.dto';
@Injectable()
export class AiService {
  // 注入 AiRecord 实体的 Repository(操作数据库)
  constructor(@InjectRepository(AiRecord) private aiRecordRepo: Repository<AiRecord>) {}
  // 模拟 AI 生成文本(后续替换为真实 LLM API 调用)
  async generateText(dto: GenerateTextDto) {
    // 1. 模拟 AI 生成结果(实际项目中替换为 OpenAI/通义千问 API 调用)
    const generatedContent = `AI 生成结果(基于提示词:${dto.prompt}):前端转全栈,用 NestJS + TypeORM 真的太香了!`;
    // 2. 把生成结果存入数据库
    const aiRecord = this.aiRecordRepo.create({
      content: generatedContent,
      type: dto.type,
      userId: dto.userId,
    });
    await this.aiRecordRepo.save(aiRecord);
    // 3. 返回结果(包含数据库记录 ID)
    return {
      success: true,
      data: {
        recordId: aiRecord.id,
        content: generatedContent,
      },
    };
  }
}
步骤 3:定义接口路由

打开 src/modules/ai/ai.controller.ts,替换为以下代码:

import { Controller, Post, Body } from '@nestjs/common';
import { AiService } from './ai.service';
import { GenerateTextDto } from './dto/generate-text.dto';
@Controller('api/ai') // 路由前缀:所有接口都以 /api/ai 开头
export class AiController {
  constructor(private readonly aiService: AiService) {}
  // 定义 POST 接口:/api/ai/generate-text
  @Post('generate-text')
  async generateText(@Body() dto: GenerateTextDto) {
    return this.aiService.generateText(dto);
  }
}

方案二:Prisma 版本接口

步骤 1:定义请求参数 DTO

新建 src/modules/ai/dto/generate-text.dto.ts:

// 定义 AI 生成请求的参数类型(前端可复用)
export class GenerateTextDto {
  prompt: string; // 提示词(如 "写一段前端学习文案")
  type: string; // 生成类型(如 "text")
  userId: string; // 关联的用户 ID(测试用,后续替换为登录用户)
}
步骤 2:编写 AI 服务逻辑

打开 src/modules/ai/ai.service.ts,替换为以下代码:

import { Injectable } from '@nestjs/common';
import { PrismaService } from 'src/prisma/prisma.service';
import { GenerateTextDto } from './dto/generate-text.dto';
@Injectable()
export class AiService {
  constructor(private prisma: PrismaService) {} // 注入 Prisma 全局服务
  // 模拟 AI 生成文本(后续替换为真实 LLM API 调用)
  async generateText(dto: GenerateTextDto) {
    // 1. 模拟 AI 生成结果(实际项目中替换为 OpenAI/通义千问 API 调用)
    const generatedContent = `AI 生成结果(基于提示词:${dto.prompt}):前端转全栈,用 NestJS + Prisma 开发效率翻倍!`;
    // 2. 把生成结果存入数据库
    const aiRecord = await this.prisma.aiRecord.create({
      data: {
        content: generatedContent,
        type: dto.type,
        userId: dto.userId,
      },
    });
    // 3. 返回结果(包含数据库记录 ID)
    return {
      success: true,
      data: {
        recordId: aiRecord.id,
        content: generatedContent,
      },
    };
  }
}
步骤 3:定义接口路由

打开 src/modules/ai/ai.controller.ts,替换为以下代码:

import { Controller, Post, Body } from '@nestjs/common';
import { AiService } from './ai.service';
import { GenerateTextDto } from './dto/generate-text.dto';
@Controller('api/ai') // 路由前缀:所有接口都以 /api/ai 开头
export class AiController {
  constructor(private readonly aiService: AiService) {}
  // 定义 POST 接口:/api/ai/generate-text
  @Post('generate-text')
  async generateText(@Body() dto: GenerateTextDto) {
    return this.aiService.generateText(dto);
  }
}

步骤 4:测试接口(用 Postman/Apifox)

  1. 确保项目处于运行状态(npm run start:dev);
  2. 打开 Postman,创建一个 POST 请求,地址:http://localhost:3000/api/ai/generate-text
  3. 请求体(Body)选择 raw → JSON,输入以下参数:
{
  "prompt": "写一段前端学习文案",
  "type": "text",
  "userId": "test-user-123" // 测试用用户 ID,后续替换为真实用户
}
  1. 发送请求,成功返回以下结果即说明接口正常工作:
{
  "success": true,
  "data": {
    "recordId": "xxx-xxx-xxx-xxx", // 自动生成的记录 ID
    "content": "AI 生成结果(基于提示词:写一段前端学习文案):前端转全栈,用 NestJS + XXX 真的太香了!"
  }
}

同时,数据库中会新增一条 AI 生成记录,验证数据库连接成功。

五、项目骨架总结与后续扩展

到这里,我们的 NestJS + TypeScript 全栈项目骨架已经搭建完成,包含:

✅ 基础环境配置(Node.js + Nest CLI + TypeScript);

✅ 核心模块结构(用户模块 + AI 模块 + 数据库模块);

✅ 数据库连接(TypeORM/Prisma 二选一,支持类型安全);

✅ 测试接口(AI 生成文本,包含数据库存储);

✅ 前端友好的类型定义(前后端可复用 interface/dto)。

这个骨架的优势:

  1. 前后端 TypeScript 类型互通 —— 前端可直接复用后端的 GenerateTextDto 类型,避免字段不一致;
  2. 模块化清晰 —— 后续新增功能(如用户登录、AI 生成代码),只需新增对应模块;
  3. 可扩展性强 —— 后续替换为真实 LLM API、切换数据库(如 MySQL/PostgreSQL)、添加权限校验,都能基于这个骨架快速扩展。

下一章预告

下一章,我们将介绍AI本地化基础,了解 AI 本地化部署的项目整体思路。