Pothos GraphQL

App layout

Pothos tries not to be opinionated about how you structure your code, and provides multiple ways of doing many things. This short guide covers a few conventions I use, as a starting place for anyone who is just looking for a decent setup that should just work. Everything suggested here is just a recommendation and is completely optional.


Here are a few files I create in almost every Pothos schema I have built:

  • src/server.ts: Setup and run your server (This might be graphql-yoga or @apollo/server)

  • src/builder.ts: Setup for your schema builder. Does not contain any definitions for types in your schema

  • src/schema.ts or src/schema/index.ts: Imports all the files that define part of your schema, but does not define types itself. Exports builder.toSchema()

  • src/types.ts: Define shared types used across your schema including a type for your context object. This should be imported when creating your builder, and may be used by many other files.

  • src/schema/*.ts: Actual definitions for your schema types.


Import types directly from the files that define them rather than importing from another file like index.ts that re-exports them. index.ts files can still be useful for loading all files in a directory, but they should generally NOT export any values.


Which plugins you use is completely up to you. For my own projects, I will use the simple-objects, scope-auth, and mocks plugins in every project, and some of the other plugins as needed.

mocks and scope-auth are fairly self explanatory. The simple-objects plugin can make building out a graph much quicker, because you don't have to have explicit types or models for every object in your graph. I frequently find that I just want to add an object of a specific shape, and then let the parent field figure out how to return an object of the right shape.

Backing models

Pothos gives you a lot of control over how you define the types that your schema and resolvers use, which can make figuring out the right approach confusing at first. In my projects, I try to avoid using the SchemaTypes approach for defining backing models. Instead, I tend to use model classes for defining most of the important objects in my graph, and fall back to using either the simple-objects plugin or builder.objectRef<Shape>(name).implement({...}) when it does not make sense to define a class for my data.

Co-locating queries

In bigger graphs, having all your queries/entry points defined in one place can become hard to manage. Instead, I prefer to define queries alongside the types they return. For example, queries for a User type would be defined in the same file that contains the definition for the User type, rather than in a central queries.ts file (using builder.queryField).