You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Apollo Server(TypeScript)如何将数据库snake_case字段映射为GraphQL camelCase字段?

GraphQL字段命名映射(snake_case到camelCase)的TypeScript类型安全问题

问题背景

我正在使用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。

我有以下问题:

  1. 在使用Apollo Server和TypeScript时,将数据库snake_case字段映射为GraphQL camelCase字段的推荐方案是什么?
  2. 是否可以配置生成的解析器父类型,让parent反映数据库模型而非GraphQL类型?
  3. 是否应该在查询解析器返回前将所有数据库结果从snake_case转换为camelCase?
  4. 在Apollo Server + GraphQL Code Generator项目中,如何在保证完整类型安全的前提下处理该场景?

例如,将数据库中的created_at以createdAt的形式暴露在GraphQL Schema中的惯用方式是什么?


解决方案与回答

1. 推荐方案

主流有两种可靠方案:

  • 全局字段转换:在数据从数据库取出后、返回给解析器前,统一将snake_case转为camelCase,一次性解决所有字段的命名映射。
  • 字段解析器+类型映射:针对需要转换的字段单独编写解析逻辑,同时通过配置让解析器的父类型匹配数据库模型,解决TypeScript类型报错问题。

2. 配置解析器父类型匹配数据库模型

完全可以通过GraphQL Code Generator的配置实现:

  1. 先定义与数据库返回结构一致的TypeScript模型:
// src/types/db.ts
export interface DBUser {
  id: number;
  created_at: string;
}
  1. 在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. 保证类型安全的完整方案

结合上述方式,推荐标准化流程:

  1. 定义与数据库结构完全一致的TypeScript模型(snake_case命名)。
  2. 配置GraphQL Code Generator的mappers,将GraphQL类型与数据库模型关联,确保解析器参数类型正确。
  3. 根据场景选择转换方式:
    • 全局转换:在数据访问层统一转换字段名,返回符合GraphQL类型的结构。
    • 字段解析器:针对特殊字段编写解析逻辑,利用映射后的数据库模型类型避免类型报错。
  4. 可选:启用GraphQL Code Generator的类型校验插件(如typescript-validation-schema),进一步强化Schema、解析器、数据库模型的类型一致性。

惯用方式

将数据库created_at暴露为GraphQL的createdAt,最常用的方式是全局字段转换+类型对齐:
在数据访问层(DAO/Repository)统一将snake_case转换为camelCase,返回与GraphQL类型结构一致的数据,配合生成的解析器类型直接返回,无需额外编写字段解析器,既保证类型安全,又减少冗余代码。


内容的提问来源于stack exchange,提问作者erkanunluturk

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.05 20:03:12