从零搭建NestJS项目(附完整代码):连接PostgreSQL+实现基础接口
作为一名后端开发者,在接触过Express、Koa等轻量框架后,第一次用NestJS就被它的模块化架构和依赖注入设计惊艳到了。它基于TypeScript,完美契合企业级开发的"高效、可扩展、易维护"需求,让后端代码不再杂乱无章。
今天就带大家从零搭建一个完整的NestJS基础项目,包含PostgreSQL数据库连接、用户登录、Todo增删查、数据库测试等核心功能,所有代码可直接复制使用,新手也能快速上手~
一、先搞懂:NestJS到底是什么?
很多人会把NestJS和Express对比,其实两者不是一个维度的东西------NestJS是基于Express(或Fastify)封装的企业级框架,它解决了Express缺乏规范、项目越大越难维护的痛点。
核心优势亮点:
- 基于TypeScript开发,强类型约束,减少运行时错误,代码更规范
- 采用模块化(Module)架构,拆分Controller(路由)、Service(业务),职责清晰
- 内置依赖注入(DI),解耦代码,便于测试和维护
- 支持RESTful API设计,语义化HTTP请求,契合现代后端开发规范
简单类比:如果说Express是"毛坯房",可以自由装修但需要自己定规则;那NestJS就是"精装房",自带成熟的设计规范,既能快速入住,也能按需改造。
二、环境准备与项目初始化
1. 安装NestJS脚手架
首先全局安装NestJS CLI,用于快速创建项目和生成各类文件:
css
npm i -g @nestjs/cli
2. 新建NestJS项目
bash
# 新建项目(项目名可自定义,这里用nest-test-demo)
nest new nest-test-demo
# 进入项目目录
cd nest-test-demo
# 启动开发服务(默认端口3000)
npm run start:dev
启动成功后,访问 http://localhost:3000,能看到NestJS默认的欢迎页面,说明项目初始化成功。
3. 安装所需依赖
本项目需要用到PostgreSQL数据库、环境变量配置等依赖,执行以下命令安装:
bash
# 安装PostgreSQL驱动和连接池
npm install pg
# 安装环境变量配置依赖
npm install dotenv
三、项目核心结构解析(附完整代码)
NestJS的核心是"模块化",一个标准的Nest项目结构如下(我们只保留核心文件,简化冗余内容):
ruby
nest-test-demo/
├── src/
│ ├── database/ # 数据库配置模块
│ │ └── database.module.ts
│ ├── todos/ # Todo功能模块(增删查)
│ │ ├── todos.controller.ts
│ │ ├── todos.service.ts
│ │ └── todos.module.ts
│ ├── app.controller.ts # 主控制器(路由入口)
│ ├── app.service.ts # 主服务(业务逻辑)
│ ├── app.module.ts # 根模块(整合所有子模块)
│ └── main.ts # 入口文件(启动服务)
└── .env # 环境变量配置
1. 入口文件:main.ts
项目的启动入口,负责创建Nest应用实例、加载环境变量、监听端口。
javascript
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { config } from 'dotenv';
// 加载.env环境变量
config();
async function bootstrap() {
// 工厂模式创建Nest应用实例(核心:加载根模块AppModule)
const app = await NestFactory.create(AppModule);
// 打印端口(从环境变量读取,默认3000)
console.log(process.env.PORT, '//////')
// 监听端口(空值合并运算符:环境变量不存在则用3000)
await app.listen(process.env.PORT ?? 3000);
}
// 启动应用
bootstrap();
2. 环境变量配置:.env
集中管理端口、数据库连接信息,避免硬编码,便于后期维护和部署。
ini
# 服务器端口
PORT=1234
# PostgreSQL数据库配置
DB_USER=postgres # 数据库用户名(默认postgres)
DB_HOST=localhost # 数据库地址(本地默认localhost)
DB_NAME=XUEBI # 数据库名称(需提前创建)
DB_PASSWORD=****** # 数据库密码(自己设置的密码)
DB_PORT=5432 # PostgreSQL默认端口
注意:不要写错配置项(比如之前踩坑的DB=PASSWORD,多写一个等号会导致密码读取失败),配置项要和代码中读取的变量名完全一致。
3. 数据库模块:database.module.ts
全局数据库配置模块,使用pg的Pool创建数据库连接池,通过依赖注入供其他模块使用。
typescript
import { Module, Global } from '@nestjs/common';
import { Pool } from 'pg'; // 引入PostgreSQL连接池
import * as dotenv from 'dotenv';
// 加载环境变量
dotenv.config();
// @Global() 装饰器:让该模块成为全局模块,其他模块无需import即可使用
@Global()
@Module({
providers: [
{
provide: 'PG_CONNECTION', // 注入标识(后续通过这个标识获取连接)
useValue: new Pool({ // 创建连接池
user: process.env.DB_USER,
host: process.env.DB_HOST,
database: process.env.DB_NAME,
password: process.env.DB_PASSWORD,
port: parseInt(process.env.DB_PORT || '5432', 10), // 端口转数字
ssl: false, // 本地连接关闭SSL(生产环境可开启)
client_encoding: 'UTF8' // 解决中文乱码问题
})
}
],
exports: ['PG_CONNECTION'] // 导出连接,供其他模块使用
})
export class DatabaseModule {}
4. 主服务:app.service.ts
处理核心业务逻辑(比如登录验证、欢迎语),被Controller依赖注入,职责是"做什么"。
typescript
import { Injectable } from '@nestjs/common'
// @Injectable() 装饰器:标记该类为可注入的服务,供Controller使用
@Injectable()
export class AppService {
// 测试接口:返回简单字符串
getHello(): string {
return '你好yeah!!!'
}
// 欢迎接口:返回欢迎语
getWelcome(): string {
return "欢迎来到nest测试项目"
}
// 登录业务逻辑:简单的用户名密码校验(实际开发需加密)
handleLogin(username: string, password: string) {
// 模拟管理员账号密码(实际开发需从数据库查询)
if (username === "admin" && password === "123456") {
return {
status: 200,
message: "登录成功"
}
} else {
return {
status: 400,
message: "登录失败"
}
}
}
}
5. 主控制器:app.controller.ts
处理HTTP请求,定义路由,接收前端参数,调用Service处理业务,职责是"接收请求、返回响应"。
typescript
import { Controller, Get, Post, Body, Inject } from '@nestjs/common'
import { AppService } from './app.service'
// @Controller() 装饰器:标记该类为控制器,处理根路径相关请求
@Controller()
export class AppController {
// 依赖注入:注入数据库连接和AppService
constructor(
@Inject('PG_CONNECTION') private readonly db: any, // 注入PostgreSQL连接
private readonly appService: AppService // 注入AppService
) {}
// GET请求:根路径(http://localhost:1234)
@Get()
getHello(): string {
return this.appService.getHello()
}
// GET请求:/welcome(http://localhost:1234/welcome)
@Get('welcome')
getWelcome(): string {
return this.appService.getWelcome()
}
// POST请求:/login(http://localhost:1234/login),处理登录
@Post('login')
login(@Body() body: { username: string, password: string }) {
const { username, password } = body;
console.log(username, password); // 打印前端传递的参数
// 参数校验(非空、密码长度)
if (!username || !password) {
return { code: 400, message: "用户名或密码不能为空" }
}
if (password.length< 6) {
return { code: 400, message: "密码不能少于6位" }
}
// 调用Service的登录方法处理业务
return this.appService.handleLogin(username, password)
}
// GET请求:/db-test(http://localhost:1234/db-test),测试数据库连接
@Get('db-test')
async testConnection() {
try {
// 查询users表(需提前创建)
const res = await this.db.query('SELECT * from users');
return {
status: '连接成功',
data: res.rows
}
} catch(error) {
return {
status: '连接失败',
error: error.message
}
}
}
}
6. Todo模块:实现基础增删查
Todo模块是一个独立的功能模块,包含Controller(路由)、Service(业务)、Module(模块),体现NestJS的模块化思想。
(1)Todo服务:todos.service.ts
typescript
import { Injectable } from '@nestjs/common'
// 定义Todo接口,强类型约束
export interface Todo {
id: number;
title: string;
completed: boolean;
}
@Injectable()
export class TodosService {
// 模拟数据库(实际开发需操作PostgreSQL)
private todos: Todo[] = [
{ id: 1, title: '周五狂欢', completed: false },
{ id: 2, title: '三角洲首胜', completed: true }
]
// 查询所有Todo
findAll() {
return this.todos
}
// 添加Todo
addTodo(title: string) {
const todo: Todo = {
id: +Date.now(), // 用时间戳作为临时ID
title,
completed: false
}
this.todos.push(todo);
return todo;
}
// 删除Todo
deleteTodo(id: number) {
this.todos = this.todos.filter(todo => todo.id !== id);
return { message: 'Todo deleted', code: 200 }
}
}
(2)Todo控制器:todos.controller.ts
typescript
import {
Controller,
Get,
Post,
Body,
Delete,
Param,
ParseIntPipe // 解析参数为整数(避免字符串ID)
} from '@nestjs/common';
import { TodosService } from './todos.service';
// @Controller('todos'):所有路由前缀为 /todos
@Controller('todos')
export class TodosController{
constructor(private readonly todosService: TodosService){}
// GET /todos:查询所有Todo
@Get()
getTodos() {
return this.todosService.findAll();
}
// POST /todos:添加Todo(接收前端传递的title参数)
@Post()
addTodo(@Body('title') title:string) {
return this.todosService.addTodo(title);
}
// DELETE /todos/:id:删除指定ID的Todo
@Delete(':id')
deleteTodo(@Param('id', ParseIntPipe) id: number) {
console.log(typeof id, '//////'); // 验证ID是否为数字
return this.todosService.deleteTodo(id);
}
}
(3)Todo模块:todos.module.ts
typescript
import { Module } from '@nestjs/common';
import { TodosController } from './todos.controller'
import { TodosService } from './todos.service'
// Todo模块:整合该功能的Controller和Service
@Module({
controllers: [TodosController], // 注册该模块的控制器
providers: [TodosService] // 注册该模块的服务
})
export class TodosModule{}
7. 根模块:app.module.ts
NestJS的根模块,负责整合所有子模块(DatabaseModule、TodosModule),是整个项目的"入口模块"。
typescript
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { TodosModule } from './todos/todos.module';
import { DatabaseModule } from './database/database.module';
// @Module() 装饰器:标记该类为根模块
@Module({
imports: [
TodosModule, // 引入Todo模块
DatabaseModule // 引入数据库模块(全局模块也可引入,更清晰)
],
controllers: [AppController], // 注册主控制器
providers: [AppService], // 注册主服务
})
export class AppModule{}
四、常见踩坑点(必看)
结合我自己搭建过程中遇到的问题,整理了4个高频踩坑点,帮大家避坑:
1. 数据库连接失败:SASL认证错误
错误提示:{"status":"连接失败","error":"SASL: SCRAM-SERVER-FIRST-MESSAGE: client password must be a string"}
原因:.env文件中密码配置项写错(比如多写等号:DB=PASSWORD=******),导致读取到的密码是undefined(非字符串)。
解决:将.env中的DB=PASSWORD=******改为DB_PASSWORD=******。
2. 数据库不存在:关系"XUEBI"不存在
错误提示:{"status":"连接失败","error":"���ݿ� "XUEBI" ������"}(中文乱码,实际是"数据库XUEBI不存在")。
原因:.env中指定的DB_NAME=XUEBI,但PostgreSQL中没有创建该数据库。
解决:登录PostgreSQL,执行CREATE DATABASE "XUEBI";创建数据库。
3. 表不存在:关系"users"不存在
错误提示:{"status":"连接失败","error":"关系 "users" 不存在"}
原因:db-test接口执行了SELECT * from users,但数据库中没有创建users表。
解决:登录XUEBI数据库,创建users表(示例SQL):
sql
CREATE TABLE users (
id SERIAL PRIMARY KEY,
username VARCHAR(50) NOT NULL,
password VARCHAR(100) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
4. psql命令找不到:bash: psql: command not found
原因:Linux系统中PostgreSQL的bin目录未添加到环境变量PATH中。
解决:将PostgreSQL的bin目录(如/usr/lib/postgresql/16/bin)添加到环境变量,永久生效:
bash
# 编辑环境变量配置文件
nano ~/.bashrc
# 添加以下内容(替换为自己的路径)
export PATH=$PATH:/usr/lib/postgresql/16/bin
# 生效配置
source ~/.bashrc
五、接口测试(Postman)
项目启动后(npm run start:dev),用Postman测试所有接口,确保功能正常:
- GET http://localhost:1234 → 返回"你好yeah!!!"
- GET http://localhost:1234/welcome → 返回"欢迎来到nest测试项目"
- POST http://localhost:1234/login(Body传{"username":"admin","password":"123456"})→ 返回登录成功
- GET http://localhost:1234/db-test → 返回数据库连接成功和users表数据
- GET http://localhost:1234/todos → 返回所有Todo
- POST http://localhost:1234/todos(Body传{"title":"学习NestJS"})→ 添加新Todo
- DELETE http://localhost:1234/todos/1 → 删除ID为1的Todo
六、总结与后续扩展
通过这个项目,我们快速掌握了NestJS的核心用法:
- 模块化架构:拆分Module、Controller、Service,职责清晰
- 依赖注入:通过@Inject、@Injectable实现代码解耦
- 数据库连接:使用pg连接PostgreSQL,配置全局数据库模块
- RESTful API:实现GET、POST、DELETE等语义化请求
后续可以基于这个基础项目扩展更多功能:
- 用户密码加密(使用bcrypt)
- 添加JWT身份验证
- 完善Todo的修改、分页查询功能
- 添加异常过滤器,统一错误响应
NestJS的学习曲线虽然比Express稍陡,但一旦掌握,就能极大提升后端开发效率,尤其适合中大型项目。本文所有代码均可直接复制使用,新手可以跟着步骤一步步搭建,遇到问题可以参考踩坑点解决