TypeScript 与后端开发Node.js

文章目录

一、搭建 TypeScript + Node.js 项目

(一)初始化项目并安装相关依赖

1、创建项目目录并初始化

首先,创建一个新的项目文件夹,例如通过命令行操作:

复制代码
mkdir my-node-ts-project
cd my-node-ts-project
npm init -y

npm init -y 命令会初始化一个 package.json 文件,用于管理项目的依赖和相关配置信息,-y 参数表示使用默认配置进行初始化。

2、安装必要的依赖包

对于一个基于 TypeScript 和 Node.js 的后端项目,需要安装以下核心依赖:

  • typescript:TypeScript 编译器,用于将 TypeScript 代码编译为 JavaScript 代码。可以通过以下命令安装:

    npm install typescript --save-dev

这里使用 --save-dev 表示将其作为开发依赖安装,因为它主要在开发阶段使用,项目运行时并不直接依赖它。

  • @types/node:这个包提供了 Node.js 相关的类型定义,使得在 TypeScript 代码中使用 Node.js 的内置模块(如 http、fs 等)时能够有类型检查和智能提示等功能,安装命令如下:

    npm install @types/node --save-dev

另外,如果计划使用一些特定的后端框架(如 Express),还需要安装对应的依赖包以及相应的类型定义包(如 @types/express)。例如安装 Express:

复制代码
npm install express
npm install @types/express --save-dev

(二)配置 TypeScript 编译选项(如模块解析方式适合后端)

在项目根目录下创建 tsconfig.json 文件,用于配置 TypeScript 的编译选项。以下是一个适合后端开发的基本配置示例:

复制代码
{
  "compilerOptions": {
    "target": "es6",
    "module": "commonjs",
    "outDir": "dist",
    "rootDir": "src",
    "strict": true,
    "esModuleInterop": true,
    "sourceMap": true,
    "resolveJsonModule": true
  },
  "include": ["src/**/*.ts"],
  "exclude": ["node_modules"]
}

解释一下关键配置项:

  • target:指定将 TypeScript 代码编译成的 JavaScript 版本,这里选择 es6,可以利用一些较新的 JavaScript 特性,同时在大多数 Node.js 环境中也能很好地支持。
  • module:设置模块系统,commonjs 是 Node.js 常用的模块规范,便于在后端环境中进行模块的导入和导出操作。
  • outDir:定义编译后 JavaScript 文件的输出目录,这里设置为 dist,意味着编译后的 .js 文件会存放在 dist 文件夹下。
  • rootDir:指定 TypeScript 源代码的根目录,通常是 src 文件夹,编译器会从这个目录及其子目录中查找 .ts 文件进行编译。
  • strict:开启严格模式,启用全面的类型检查等功能,有助于提高代码质量,减少潜在错误。
  • esModuleInterop:解决在 CommonJS 和 ES 模块之间互操作时可能出现的一些问题,方便在项目中同时使用不同模块规范引入的依赖。
  • sourceMap:生成源映射文件(.map 文件),在调试时能够将编译后的 JavaScript 代码映射回原始的 TypeScript 代码,方便查找问题。
  • resolveJsonModule:允许在 TypeScript 中直接导入 .json 文件,并将其解析为相应的 JavaScript 对象,这在后端项目中处理配置文件等情况时很有用。

二、编写服务器代码

(一)定义路由类型(使用 Express 等框架)

以 Express 框架为例,在 TypeScript 中定义路由时,可以先创建一个路由模块,然后明确路由处理函数的类型。

例如,创建一个简单的 userRoutes.ts 文件用于定义用户相关的路由:

复制代码
import express from 'express';
import { Request, Response } from 'express';

const router = express.Router();

// 定义一个获取用户信息的路由
router.get('/users/:id', (req: Request, res: Response) => {
  const userId = req.params.id;
  // 这里可以假设从数据库等地方获取用户信息并返回,暂时模拟返回一个固定信息
  const user = { id: userId, name: 'John Doe' };
  res.json(user);
});

export default router;

在上述代码中:

  • 首先导入了 express 框架以及 express 中的 Request 和 Response 类型定义,它们分别用于描述 HTTP 请求和响应的相关结构和属性类型。
  • 然后使用 express.Router() 创建了一个路由实例 router,并定义了一个 GET 类型的路由 /users/:id,其处理函数接收 Request 和 Response 类型的参数,在函数内部可以通过 req.params 获取路由参数(这里是用户 ID),并根据业务逻辑返回相应的用户信息(这里简单地返回了模拟数据,实际中可能会从数据库查询等操作获取真实数据),最后通过 res.json() 方法将数据以 JSON 格式返回给客户端。

(二)处理请求和响应的类型(包括中间件的类型)

1、请求类型处理

除了基本的路由参数获取,请求对象(Request)还包含很多其他属性,比如请求头(headers)、请求体(body)等,在 TypeScript 中可以对它们进行相应的类型定义和处理。

例如,创建一个接收用户注册信息的路由,需要处理请求体中的 JSON 数据(假设用户注册信息包含用户名和密码):

复制代码
import express from 'express';
import { Request, Response } from 'express';

interface UserRegistration {
  username: string;
  password: string;
}

const router = express.Router();

router.post('/register', (req: Request<{}, {}, UserRegistration>, res: Response) => {
  const userData: UserRegistration = req.body;
  console.log(`Received registration data: ${JSON.stringify(userData)}`);
  // 这里可以进行后续的业务逻辑,比如将用户信息存入数据库等操作
  res.status(201).send('Registration successful');
});

export default router;

在这个示例中,定义了 UserRegistration 接口来描述用户注册信息的结构(包含用户名和密码两个属性,类型都为 string),在路由处理函数的 Request 类型参数中,通过泛型指定了请求体的类型为 UserRegistration,这样在函数内部就能安全地从 req.body 获取并使用用户注册信息了,编译器会确保类型的一致性。

2、响应类型处理

对于响应对象(Response),可以根据不同的业务需求设置响应状态码、响应头以及返回不同格式的数据等,同样要遵循类型规范。

例如,在一个返回文件下载的路由中,需要正确设置响应头来指示文件类型等信息:

复制代码
import express from 'express';
import { Request, Response } from 'express';
import path from 'path';
import fs from 'fs';

const router = express.Router();

router.get('/download/:filename', (req: Request, res: Response) => {
  const fileName = req.params.filename;
  const filePath = path.join(__dirname, 'uploads', fileName);
  const fileStream = fs.createReadStream(filePath);
  res.setHeader('Content-Type', 'application/octet-stream');
  res.setHeader('Content-Disposition', `attachment; filename="${fileName}"`);
  fileStream.pipe(res);
});

export default router;

在这个示例中,设置了响应头的 Content-Type 和 Content-Disposition 属性,用于告知客户端这是一个文件流下载,并且指定了文件名等信息,整个过程中 res 的各种方法调用(如 setHeader、pipe 等)都符合 Response 类型的定义和要求,确保了响应操作的正确性。

3、中间件类型处理

Express 框架中的中间件在 TypeScript 中也需要进行类型定义,以确保其在处理请求和传递控制流时的类型安全。

例如,创建一个简单的日志记录中间件,用于记录每个请求的相关信息:

复制代码
import { Request, Response, NextFunction } from 'express';

const loggerMiddleware = (req: Request, res: Response, next: NextFunction) => {
  console.log(`Received request: ${req.method} ${req.url}`);
  next();
};

export default loggerMiddleware;

在这个中间件函数中,参数按照 Request、Response 和 NextFunction(用于调用下一个中间件或路由处理函数的函数类型)的顺序定义,在中间件内部可以访问和处理请求相关的信息,然后通过调用 next() 函数将控制流传递给下一个中间件或路由处理函数,遵循了正确的类型规范。

三、数据库交互

(一)使用 Type - Safe 的数据库驱动(如 TypeORM)

TypeORM 是一个非常流行的支持 Type - Safe(类型安全)的 Node.js 数据库 ORM(对象关系映射)框架,它允许使用面向对象的方式与数据库进行交互,并且在整个过程中利用 TypeScript 的类型系统确保类型的准确性。

1、安装 TypeORM 及相关依赖

首先需要安装 TypeORM 以及对应数据库的驱动(以 MySQL 为例),同时还要安装 TypeORM 的类型定义包,命令如下:

复制代码
npm install typeorm mysql2 @types/mysql2
2、配置数据库连接

在项目中创建一个 ormconfig.json 文件(也可以使用 JavaScript 或 TypeScript 文件来配置,这里以 .json 文件为例)用于配置数据库连接相关信息,示例如下:

复制代码
{
  "type": "mysql",
  "host": "localhost",
  "port": 3306,
  "username": "root",
  "password": "your_password",
  "database": "your_database_name",
  "synchronize": true,
  "logging": true,
  "entities": ["src/entities/*.ts"],
  "migrations": ["src/migrations/*.ts"],
  "subscribers": ["src/subscribers/*.ts"]
}

解释关键配置项:

  • type:指定数据库类型,这里是 mysql。
    host、port、username、password、database:分别对应数据库的主机地址、端口号、用户名、密码和数据库名称,根据实际情况进行填写。
  • synchronize:设置为 true 时,TypeORM 会自动根据实体类(后面会介绍)的定义来创建、更新数据库表结构,但在生产环境中要谨慎使用,建议使用数据库迁移(migrations)来管理表结构变化。
  • logging:开启日志记录,方便查看数据库操作的相关情况,比如执行的 SQL 语句等。
  • entities:定义了实体类文件的路径,实体类用于映射数据库中的表结构,TypeORM 会根据这些实体类来进行数据库操作。
  • migrations 和 subscribers:分别用于配置数据库迁移文件和订阅者文件的路径,用于更复杂的数据库结构变更管理和事件监听等功能。

(二)定义数据库模型和操作的类型(增删改查)

1、定义数据库模型(实体类)

使用 TypeORM,需要创建实体类来描述数据库中的表结构以及表与表之间的关系。例如,创建一个简单的 User 实体类,对应数据库中的 users 表:

复制代码
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';

@Entity()
class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  username: string;

  @Column()
  password: string;
}

export default User;

在上述代码中:

  • 使用 @Entity() 装饰器标记这个类是一个实体类,对应数据库中的一张表。
  • @PrimaryGeneratedColumn() 装饰器用于定义主键列,这里表示 id 列是自增的主键,类型为 number。
  • @Column() 装饰器用于定义普通列,如 username 和 password 列,分别对应数据库表中的相应字段,类型都为 string。
2、数据库操作的类型安全实现(增删改查)

基于上述定义的实体类,可以进行各种数据库操作,并且在操作过程中保持类型安全。

例如,进行简单的查询操作,获取所有用户信息:

复制代码
import { getConnection } from 'typeorm';
import User from './entities/User';

async function getUsers() {
  const connection = await getConnection();
  const users = await connection.getRepository(User).find();
  return users;
}

// 使用示例
(async () => {
  const allUsers = await getUsers();
  console.log(allUsers);
})();

在这个查询示例中,首先通过 getConnection() 方法获取数据库连接,然后使用 connection.getRepository(User) 获取 User 实体类对应的数据库操作仓库,最后调用 find() 方法来查询所有用户信息,返回的 users 结果类型是 User[],即一个 User 类型的数组,编译器能够清楚知道查询结果的结构,方便后续进行处理(比如遍历用户列表、访问用户的各个属性等操作)。

对于插入数据(新增用户)操作,示例如下:

复制代码
import { getConnection } from 'typeorm';
import User from './entities/User';

async function createUser(newUser: User) {
  const connection = await getConnection();
  const userRepository = connection.getRepository(User);
  return await userRepository.save(newUser);
}

// 使用示例
(async () => {
  const newUser: User = {
    username: 'new_user',
    password: 'new_password'
  };
  const createdUser = await createUser(newUser);
  console.log(createdUser);
})();

在插入操作中,定义了 createUser 函数接收一个 User 类型的参数 newUser,然后将其保存到数据库中,返回的 createdUser 同样是 User 类型,确保了新增数据的类型一致性,整个数据库的增删改查操作在 TypeORM 的帮助下都能很好地遵循 TypeScript 的类型规范,减少因类型不匹配等问题导致的错误,提高后端代码的质量和可维护性。

相关推荐
wycode29 分钟前
Vue2实践(2)之用component做一个动态表单(一)
前端·javascript·vue.js
第七种黄昏31 分钟前
Vue3 中的 ref、模板引用和 defineExpose 详解
前端·javascript·vue.js
我是哈哈hh1 小时前
【Node.js】ECMAScript标准 以及 npm安装
开发语言·前端·javascript·node.js
张元清1 小时前
电商 Feeds 流缓存策略:Temu vs 拼多多的技术选择
前端·javascript·面试
pepedd8642 小时前
浅谈js拷贝问题-解决拷贝数据难题
前端·javascript·trae
@大迁世界2 小时前
useCallback 的陷阱:当 React Hooks 反而拖了后腿
前端·javascript·react.js·前端框架·ecmascript
小高0072 小时前
📌React 路由超详解(2025 版):从 0 到 1 再到 100,一篇彻底吃透
前端·javascript·react.js
Data_Adventure2 小时前
Java 与 TypeScript 的“同名方法”之争:重载机制大起底
前端·typescript
summer7772 小时前
GIS三维可视化-Cesium
前端·javascript·数据可视化
HWL56792 小时前
pnpm(Performant npm)的安装
前端·vue.js·npm·node.js