为何创建Mapper实现Entity与DTO互转?求Express/NestJS示例
为什么用Mapper类实现DTO与Entity的相互转换?
核心原因与收益
- 职责拆分更清晰:Entity是和数据库表绑定的模型,管数据持久化;DTO是接口与前端约定的数据结构,管前后端数据交互。Mapper专门负责两者的转换,不让Entity或DTO掺合不属于自己的逻辑,代码维护起来更省心。
- 消灭重复代码:如果每个接口都手动写
{ id: user.id, username: user.username... }这类转换逻辑,重复代码会堆得老高。把转换逻辑集中在Mapper里,改一处就能同步所有用到转换的地方。 - 避免敏感数据泄露:Entity里难免有密码哈希、权限等级这类敏感字段,Mapper可以直接过滤掉这些字段,不用每次返回数据都手动删除,从根源上防止不小心把敏感数据返回给前端。
- 适配多场景更灵活:同一个Entity可能对应多个DTO——比如列表页只需要用户名和ID,详情页需要完整信息。Mapper可以写不同的转换方法,不用改Entity本身就能适配不同接口的需求。
NestJS示例
先定义数据库实体(Entity)和数据传输对象(DTO):
// user.entity.ts import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm'; @Entity() export class UserEntity { @PrimaryGeneratedColumn() id: number; @Column() username: string; @Column() passwordHash: string; // 敏感字段,禁止返回前端 @Column() email: string; @Column() role: string; } // user.dto.ts export class UserDTO { id: number; username: string; email: string; role: string; }
接下来写专门的Mapper类,注入到NestJS的依赖容器中:
// user.mapper.ts import { Injectable } from '@nestjs/common'; import { UserEntity } from './user.entity'; import { UserDTO } from './user.dto'; @Injectable() export class UserMapper { // 单个Entity转DTO toDTO(entity: UserEntity): UserDTO { const dto = new UserDTO(); dto.id = entity.id; dto.username = entity.username; dto.email = entity.email; dto.role = entity.role; // 主动忽略passwordHash,保证数据安全 return dto; } // DTO转Entity(用于创建/更新用户) toEntity(dto: Partial<UserDTO>): UserEntity { const entity = new UserEntity(); entity.username = dto.username; entity.email = dto.email; entity.role = dto.role || 'user'; // 设置默认角色 return entity; } // 批量转换Entity列表 toDTOList(entities: UserEntity[]): UserDTO[] { return entities.map(entity => this.toDTO(entity)); } }
在Service中注入Mapper,直接调用转换方法:
// user.service.ts import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { UserEntity } from './user.entity'; import { UserDTO } from './user.dto'; import { UserMapper } from './user.mapper'; @Injectable() export class UserService { constructor( @InjectRepository(UserEntity) private userRepo: Repository<UserEntity>, private userMapper: UserMapper, ) {} async getAllUsers(): Promise<UserDTO[]> { const users = await this.userRepo.find(); return this.userMapper.toDTOList(users); } async createUser(userDTO: UserDTO): Promise<UserDTO> { const userEntity = this.userMapper.toEntity(userDTO); const savedUser = await this.userRepo.save(userEntity); return this.userMapper.toDTO(savedUser); } }
Express示例
Express更轻量化,Mapper可以写成普通工具类,这里用Mongoose作为数据库ORM举例:
首先定义数据库模型(对应Entity):
// user.model.js const mongoose = require('mongoose'); const userSchema = new mongoose.Schema({ username: String, passwordHash: String, email: String, role: String }); module.exports = mongoose.model('User', userSchema);
编写UserMapper类:
// user.mapper.js class UserMapper { // Entity转返回给前端的DTO toDTO(userEntity) { return { id: userEntity._id.toString(), username: userEntity.username, email: userEntity.email, role: userEntity.role }; } // 前端传入的DTO转Entity(用于创建用户) toEntity(userDTO) { return { username: userDTO.username, email: userDTO.email, role: userDTO.role || 'user' }; } // 批量转换 toDTOList(userEntities) { return userEntities.map(entity => this.toDTO(entity)); } } module.exports = new UserMapper();
在路由中使用Mapper处理数据转换:
// user.routes.js const express = require('express'); const router = express.Router(); const User = require('./user.model'); const userMapper = require('./user.mapper'); router.get('/users', async (req, res) => { try { const users = await User.find(); const userDTOs = userMapper.toDTOList(users); res.json(userDTOs); } catch (err) { res.status(500).json({ error: err.message }); } }); router.post('/users', async (req, res) => { try { const userEntity = userMapper.toEntity(req.body); const newUser = await User.create(userEntity); const userDTO = userMapper.toDTO(newUser); res.status(201).json(userDTO); } catch (err) { res.status(400).json({ error: err.message }); } }); module.exports = router;
内容的提问来源于stack exchange,提问作者Normye
相关产品推荐
相关产品推荐

