Schema Conventions
Naming conventions, type patterns, and style guide for the GraphQL schema.
Naming conventions
| Element | Casing | Example |
|---|---|---|
| Fields and arguments | camelCase | createdAt, firstName |
| Type names | PascalCase | BlogAuthor, ProductContent |
| List return fields | List suffix | blogEntryList, productList |
| ID fields | GraphQLID type | id: ID! |
| Date fields | DateTime scalar | createdAt: DateTime |
| HBML content | HbmlRaw scalar | description: HbmlRaw |
Avoid descriptions that repeat the type name — they add no value.
Type definitions
- Use
InputTypeComposerfromgraphql-composefor input types. - Use
TypeComposerfromgraphql-composefor object types. - Do not add unnecessary suffixes like
MutationInput,Enum, orType.
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:
| Operation | Pattern | Example |
|---|---|---|
| Create | {entity}Create | productCreate |
| Update | {entity}Update | productUpdate |
| Delete | {entity}Delete | productDelete |
- Create mutations accept an
inputargument with aCreateInput-suffixed type. - Update mutations accept
id(GraphQLID) andinputwith anUpdateInput-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
Listsuffix containing anodesfield ofGraphQLList. - Paginated fields must implement the
PaginatedListinterface, supportinglimitandoffsetarguments.
Translation fields
Translated content requires two fields:
- Current language — returns the value in the request language. Do not include in input types. Example:
description: String. - 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.