Apollo Server(TypeScript)如何将数据库snake_case字段映射为GraphQL camelCase字段?
问题背景
我正在使用Apollo Server、TypeScript和GraphQL Code Generator进行开发。我的GraphQL Schema采用camelCase命名的字段:
type User { id: ID! createdAt: String! }
数据库返回的数据采用snake_case命名:
const user = { id: 1, created_at: "2025-01-01T10:00:00Z", };
我的查询解析器直接返回数据库对象:
const resolvers: Resolvers = { Query: { user: async () => { return user; }, }, };
我希望通过字段解析器将created_at映射为createdAt:
const resolvers: Resolvers = { User: { createdAt: (parent) => { return parent.created_at; }, }, };
但TypeScript报错:
Property 'created_at' does not exist on type 'User'.
原因是生成的解析器类型采用GraphQL的结构(createdAt),但运行时解析器接收的是原始数据库对象(created_at),导致TypeScript只识别parent.createdAt,而实际需要的是parent.created_at。
我有以下问题:
- 在使用Apollo Server和TypeScript时,将数据库snake_case字段映射为GraphQL camelCase字段的推荐方案是什么?
- 是否可以配置生成的解析器父类型,让
parent反映数据库模型而非GraphQL类型? - 是否应该在查询解析器返回前将所有数据库结果从snake_case转换为camelCase?
- 在Apollo Server + GraphQL Code Generator项目中,如何在保证完整类型安全的前提下处理该场景?
例如,将数据库中的created_at以createdAt的形式暴露在GraphQL Schema中的惯用方式是什么?
解决方案与回答
1. 推荐方案
主流有两种可靠方案:
- 全局字段转换:在数据从数据库取出后、返回给解析器前,统一将snake_case转为camelCase,一次性解决所有字段的命名映射。
- 字段解析器+类型映射:针对需要转换的字段单独编写解析逻辑,同时通过配置让解析器的父类型匹配数据库模型,解决TypeScript类型报错问题。
2. 配置解析器父类型匹配数据库模型
完全可以通过GraphQL Code Generator的配置实现:
- 先定义与数据库返回结构一致的TypeScript模型:
// src/types/db.ts export interface DBUser { id: number; created_at: string; }
- 在GraphQL Code Generator配置文件(如
codegen.ts)中,通过mappers选项将GraphQL类型映射到数据库模型:
// codegen.ts import type { CodegenConfig } from '@graphql-codegen/cli'; const config: CodegenConfig = { schema: './src/schema.graphql', generates: { './src/generated/graphql.ts': { plugins: ['typescript', 'typescript-resolvers'], config: { mappers: { User: './types/db#DBUser', // 关联GraphQL User类型与数据库DBUser类型 }, }, }, }, }; export default config;
重新生成类型后,字段解析器中的parent参数类型会自动变为DBUser,此时访问parent.created_at不会再有TypeScript报错:
const resolvers: Resolvers = { User: { createdAt: (parent) => { return parent.created_at; // 类型校验通过 }, }, };
3. 是否在查询解析器返回前转换字段
取决于项目规模和需求:
- 如果大部分模型都需要做命名转换,全局转换更高效,可以避免重复编写字段解析器。可以借助
change-case、lodash等工具库批量处理:
import { camelCaseKeys } from 'change-case'; const resolvers: Resolvers = { Query: { user: async () => { const dbUser = await fetchUserFromDB(); return camelCaseKeys(dbUser); // 转换后返回{ id: 1, createdAt: "2025-01-01T10:00:00Z" } }, }, };
这种方式下,返回的结构完全匹配GraphQL生成的类型,无需额外字段解析器,类型安全有保障。
- 如果只有少数字段需要特殊处理,或者部分字段包含自定义业务逻辑,字段解析器+类型映射的方式更灵活。
4. 保证类型安全的完整方案
结合上述方式,推荐标准化流程:
- 定义与数据库结构完全一致的TypeScript模型(snake_case命名)。
- 配置GraphQL Code Generator的
mappers,将GraphQL类型与数据库模型关联,确保解析器参数类型正确。 - 根据场景选择转换方式:
- 全局转换:在数据访问层统一转换字段名,返回符合GraphQL类型的结构。
- 字段解析器:针对特殊字段编写解析逻辑,利用映射后的数据库模型类型避免类型报错。
- 可选:启用GraphQL Code Generator的类型校验插件(如
typescript-validation-schema),进一步强化Schema、解析器、数据库模型的类型一致性。
惯用方式
将数据库created_at暴露为GraphQL的createdAt,最常用的方式是全局字段转换+类型对齐:
在数据访问层(DAO/Repository)统一将snake_case转换为camelCase,返回与GraphQL类型结构一致的数据,配合生成的解析器类型直接返回,无需额外编写字段解析器,既保证类型安全,又减少冗余代码。
内容的提问来源于stack exchange,提问作者erkanunluturk

