Holibob Docs

Schema Conventions

Naming conventions, type patterns, and style guide for the GraphQL schema.

Naming conventions

ElementCasingExample
Fields and argumentscamelCasecreatedAt, firstName
Type namesPascalCaseBlogAuthor, ProductContent
List return fieldsList suffixblogEntryList, productList
ID fieldsGraphQLID typeid: ID!
Date fieldsDateTime scalarcreatedAt: DateTime
HBML contentHbmlRaw scalardescription: HbmlRaw

Avoid descriptions that repeat the type name — they add no value.

Type definitions

  • Use InputTypeComposer from graphql-compose for input types.
  • Use TypeComposer from graphql-compose for object types.
  • Do not add unnecessary suffixes like MutationInput, Enum, or Type.

Good: ActorType, AdminStats, AnalyticLinkOriginCreateInput Bad: ActorTypeEnum, AdminStatsType, AnalyticLinkOriginCreateMutationInput

Use NonNull for fields that must never be nullable and custom scalars (e.g. PositiveInt) for tightly constrained values.

Mutation patterns

CRUD mutations

Follow the [noun][verb] format:

OperationPatternExample
Create{entity}CreateproductCreate
Update{entity}UpdateproductUpdate
Delete{entity}DeleteproductDelete
  • Create mutations accept an input argument with a CreateInput-suffixed type.
  • Update mutations accept id (GraphQLID) and input with an UpdateInput-suffixed type.
  • Both must return a non-nullable type.
  • Use distinct Create and Update mutations instead of upsert. If upsert is used, it must return a non-nullable type.

Non-CRUD mutations

Prefer individual arguments over wrapping everything in an input object:

# Preferred
mutation ToggleSwitcher($id: String!, $state: Boolean!) {
    toggleSwitcher(id: $id, state: $state)
}

# Avoid
mutation ToggleSwitcher($input: ToggleSwitcherInput!) {
    toggleSwitcher(input: $input)
}

Resolver structure

Resolvers live in schema/resolvers/ and follow this pattern:

import { MutationsSomeArgs } from "@holibob-packages/graphql-types";

import { GraphQLContext } from "../GraphQLContext";

export async function someResolver(_: unknown, args: MutationsSomeArgs, context: GraphQLContext) {
    // resolver logic
}

Separate resolver logic from mutation/query definitions. Mutation files define the GraphQL field shape; resolvers contain the implementation.

List and paginated types

  • List fields return a type with a List suffix containing a nodes field of GraphQLList.
  • Paginated fields must implement the PaginatedList interface, supporting limit and offset arguments.

Translation fields

Translated content requires two fields:

  1. Current language — returns the value in the request language. Do not include in input types. Example: description: String.
  2. All languages — returns translations for every language. Suffix with TranslationList. Example: descriptionTranslationList: LanguageTranslation!. Include this in input types.

Asset handling

Use the Asset type for fields returning asset URLs. Never expose raw asset IDs — wrap them as AssetURL references.