Skip to main content

Source: ocean/docs/adr/ADR-039-graphql-snake-case-standardization.md | ✏️ Edit on GitHub

ADR-039: GraphQL Snake Case Standardization

Status

Accepted

Context

Our GraphQL API had inconsistent field naming conventions:

  • Database schema used snake_case (e.g., stripe_customer_id)
  • GraphQL schema mixed camelCase and snake_case
  • Resolvers performed manual transformations between naming conventions
  • This created unnecessary complexity and potential for errors

Decision

Standardize all GraphQL field names to snake_case to match the database schema.

Changes Made

  1. GraphQL Schema Updates:

    • All type fields converted to snake_case
    • All query and mutation names converted to snake_case
    • All input type fields converted to snake_case
    • All enum values kept in UPPER_CASE (GraphQL convention)
  2. Resolver Updates:

    • Removed manual field name transformations
    • Updated all resolver function names to snake_case
    • Updated argument destructuring to use snake_case
    • Return objects now directly match database fields
  3. Client Updates:

    • GraphQL fragments updated to use snake_case
    • Generated TypeScript types regenerated
    • ESLint configured to ignore generated files

Consequences

Positive

  • Consistency: Single naming convention throughout the stack
  • Simplicity: No field name transformations needed
  • Performance: Reduced CPU overhead from transformations
  • Maintainability: Easier to trace fields from database to API
  • Type Safety: Direct mapping between database and GraphQL types

Negative

  • Breaking Change: All GraphQL clients must update their queries
  • Convention: snake_case in GraphQL is less common than camelCase
  • Migration: Existing clients need updates

Migration Path

  1. Update all client queries to use snake_case fields
  2. Update any cached query results
  3. Regenerate client-side GraphQL types
  4. Test all GraphQL operations

Technical Details

Before

type Organization {
id: ID!
stripeCustomerId: String
createdAt: String!
}

type Query {
myOrganizations: [Organization!]!
organizationMembers(organizationId: ID!): [OrganizationMember!]!
}

After

type Organization {
id: ID!
stripe_customer_id: String
created_at: String!
}

type Query {
my_organizations: [Organization!]!
organization_members(organization_id: ID!): [OrganizationMember!]!
}

Resolver Before

return {
stripeCustomerId: organization.stripe_customer_id,
createdAt: organization.created_at,
}

Resolver After

return {
stripe_customer_id: organization.stripe_customer_id,
created_at: organization.created_at,
}
  • ADR-036: Unified Field Naming Convention
  • ADR-037: Comprehensive Code Deduplication
  • ADR-038: Edge Function Wrapper Pattern