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.
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.
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
).