Guides
Source Types
Source Types in theory
As you begin to implement a schema for the first time you will notice something that may not have been obvious at first. The data that the client sees in the data graph is not the same data flowing through the internal resolvers used to fulfill that graph. The client sees the API types but the API author deals with something else, Source Types.
To understand the concept better, let's assume the following GraphQL query:
1{2 user {3 fullName4 }5}
Here is an example of resolution for that query as it would be seen roughly from a GraphQL type only point of view.
When a field's type is an object, then the field's resolver returns a Source Type. Concretely this might for example be a plain JavaScript object containing node/row/document data from a database call. This Source Type data is in turn passed down to all the object type's own field resolvers.
Here is the above diagram updated to include Source Types now.
Here is a step-by-step breakdown of what is going on:
Client sends a query
The field resolver for
Query.user
runs. RememberQuery
fields (along withSubscription
,Mutation
) are entrypoints, also known as root fields because they are on Root Types.Within this resolver, the database client fetches a user from the database. The resolver returns this data. Keep in mind that at this point, this data may be completely different than what the GraphQL API will resolve at the end.
1// Field of `Query` type2t.field('user', {3 resolve(source, args, ctx, info) {4 // ^---- as: User (Source Type)5 return { firstname: 'Foo', lastname: 'Bar' } // fake db record6 }7})Resolution continues since the type of
Query.user
field is an object, not a scalar. As such its own fields need resolving. The fields that get resolved are limited to those selected by the client, in this case:fullName
. This field resolver run. Itssource
argument is the user model data fetched in step 3. This is the Source Type data for the GraphQLUser
object.1// Field of `User` type2t.field('fullName', {3 resolve(source, args, ctx, info) {4 // ^------------------------------- as: User (Source Type)5 },6}
Hopefully you can see how the GraphQL types seen by the client are distinct from the Source Types flowing through the resolvers. Below, you can find a code sample of how the implementation of this schema might look like.
1import { queryType, objectType } from 'nexus'23export const Query = queryType({4 definition(t) {5 t.field('user', {6 type: 'User',7 args: {8 id: nonNull(idArg()),9 },10 resolve(_, { id }, { db }) {11 return db.fetchUser({ where: { id } })12 },13 })14 },15})1617export interface UserSourceType {18 firstname: string19 lastname: string20}2122export const User = objectType({23 name: 'User',24 sourceType: {25 path: __filename,26 name: 'UserSourceType'27 },28 definition(t) {29 t.string('fullName', {30 resolve(source) {31 return source.firstname + source.lastname32 },33 })34 },35})
Source Types in Nexus
In Nexus, a Source Type can expressed as an interface
, a class
, a type
alias or an enum
.
Below are examples of valid Source Types:
1export interface UserEntity {2 id: string3 name: string4}56export class Post {7 id: string8 title: string9}1011export type CommentModel = {12 id: string13 body: string14}1516export enum Color {17 RED18 GREEN19 BLUE20}
There are many ways you can configure your Source Types depending on your use-cases. We'll go over each of the available options.
GraphQL Schema Strategy (default)
When you first begin creating your schema, you may have objects without Source Types setup. In these cases Nexus infers that the Source Type is an exact match of the GraphQL type. Take this schema for example:
1// Nexus infers the Source Type of:2//3// { fullName: string, age: number } ---> |4// |5object({ // |6 name: 'User', // |7 definition(t) { // |8 t.string('fullName', { // |9 resolve(user) { // |10// ^-------------------------- |11 return user.fullName // |12 }, // |13 }) // |14 t.int('age', { // |15 resolve(user) { // |16// ^-------------------------- |17 return user.age // |18 }, // |19 }) // |20 }, // |21}) // |22 // |23queryType({ // |24 definition(t) { // |25 t.list.field('users', { // |26 type: 'User', // |27 resolve() { // |28 return [/**/] // |29// ^----------------------- |30 },31 })32 },33})
This may suffice well enough for some time, but most apps will eventually see their GraphQL and Source Types diverge. Once this happens, you can tell Nexus about it using one of the solutions explained below.
Globally configure Source Types
Configuring your Source Types globally is the recommended way in most cases. Typically, your Source Types will come from your database model types (also called entities), or will be generated by your ORM.
When that's the case, you can configure Nexus to automatically map your Source Types to your GraphQL Object types based on their name.
This is all done in the sourceTypes
option of makeSchema
.
Exact Matching Strategy
When configured, Nexus will automatically map the Source Types that have the same name as your GraphQL Object types.
Let's see how that works:
1// ./db.ts23export interface User {4// ^-------------^---------- Export your Source Type (required)5// |---------- Name of your Source Type which will be extracted by Nexus6 firstName: string7 lastName: string8}910export interface Post {11// ^-------------^---------- Export your Source Type (required)12// |---------- Name of your Source Type which will be extracted by Nexus13// ...14}1516export interface Comment {17// ^-------------^---------- Export your Source Type (required)18// |---------- Name of your Source Type which will be extracted by Nexus19// ...20}2122// ./schema.ts2324import { makeSchema, objectType, queryType } from 'nexus'25import * as path from 'path'2627const User = objectType({28 name: 'User',29 // ^-------------- Name of your GraphQL Object type which will be matched against30 definition(t) {31 t.string('fullName', {32 resolve(user) {33 // ^------------------------ as: User34 return user.firstName + ' ' + user.lastName35 }36 })37 }38})3940const Query = queryType({41 definition(t) {42 t.list.field('users', {43 type: User,44 resolve(_parent, args, ctx) {45 // ... ^------- return as: User[]46 }47 })48 }49})5051const schema = makeSchema({52 types: [User, Query],53 sourceTypes: {54 modules: [{55 module: path.join(__dirname, 'db.ts'),56 // ^---------------------- Path to the file containing your Source Types57 alias: 'db'58 // ^----- Arbitrary unique name used as alias to import your Source Types.59 // eg: import * as <alias> from <source>60 // Make sure to use unique aliases if you have multiple sources61 }]62 }63})
In the example above, since the Source Type interface User
matches the name of the User
GraphQL Object type, Nexus will use the interface User
as Source Type for the User
GraphQL Object type.
Pattern Matching Strategy
It is however possible that your Source Types don't exactly match the name of your GraphQL Object type but follow a convention.
For instance:
- Source Type
class UserModel {}
-> GraphQL Object typeUser
- Source Type
class PostModel {}
-> GraphQL Object typePost
- Source Type
class CommentModel {}
-> GraphQL Object typeComment
In that case, you can configure your source
to teach Nexus about your convention.
To extract Source Types, Nexus runs regexes against your literal source code. The regexes used by default is:
1new RegExp(`(?:interface|type|class|enum)\\s+(${type.name})\\W`, "g")
Your regular expression must capture a group containing the TypeScript type name of the Source Type. Below is a complete and annotated example to illustrate the system
1//db.ts23export class UserModel {4// ^-------------^---------- Export your Source Type (required)5// |---------- Name of your Source Type which will be extracted by Nexus6 firstName: string7 lastName: string8}910export class PostModel {11// ^-------------^---------- Export your Source Type (required)12// |---------- Name of your Source Type which will be extracted by Nexus13// ...14}1516export class CommentModel {17// ^-------------^---------- Export your Source Type (required)18// |---------- Name of your Source Type which will be extracted by Nexus19// ...20}2122// schema.ts23import { makeSchema, objectType, queryType } from 'nexus'24import * as path from 'path'2526const User = objectType({27 name: 'User',28 // ^-------------- Name of your GraphQL Object type which will be matched against29 definition(t) {30 t.string('fullName', {31 resolve(user) {32 // ^------------------------ as: UserModel33 return user.firstName + ' ' + user.lastName34 }35 })36 }37})3839const Query = queryType({40 definition(t) {41 t.list.field('users', {42 type: User,43 resolve(_parent, args, ctx) {44 // ... ^------- return as: UserModel[]45 }46 })47 }48})4950const schema = makeSchema({51 types: [User, Query],52 sourceTypes: {53 modules: [{54 module: path.join(__dirname, 'db.ts'),55 // ^----------------------------- Path to the file containing your Source Types56 alias: 'db',57 // ^------------------------------ Name used as alias for Nexus to import your Source Types. eg: import * as <name> from <path>58 typeMatch(type, defaultRegex) {59 // ^--------^---------------- GraphQL Named type to match against your Source Types60 // |---------------- Default regex used to match your GraphQL types against your Source Types61 return new RegExp(`(?:interface|type|class|enum)\\s+(${type.name}Model)\\W`, 'g')62 // ^---------------------- New regex which will match `UserModel` with `User`, etc..63 },64 }]65 }66})
You can also use multiple regexes to extract your Source Types by returning an array of regexes.
In this case, each regex will be run against your source code in the order in which you defined them.
Note: The first regex to match match will be used.
1const schema = makeSchema({2 sourceTypes: {3 modules: [{4 module: path.join(__dirname, 'db.ts'),5 alias: 'db',6 typeMatch(type, defaultRegex) {7 return [8 defaultRegex,9 // ^---------------- Pass in the default Regex to keep the default behavior10 new RegExp(`(?:interface|type|class|enum)\\s+(${type.name}Model)\\W`, 'g'),11 // new RegExp(...), -> Add more regexes if you have even more convention12 ]13 },14 }]15 }16})
Manual Mapping Strategy
If you happen to have Source Types names that can't be matched against your GraphQL Object type names using a RegEx, there are two solutions.
The first solution is especially useful when most of your types can be matched using a RegEx but you only have a couple of types that can't. In that case, the mapping
can help you.
When provided, it will be used for the Source Types rather than the auto-resolve mechanism of sources
. It's also useful as an override for one-off cases or for scalar Source Types.
1//db.ts23export class SomeRandomName {4// ^-------------^------- Export your Source Type (required)5// |------- Name of your Source Type that you'll use for the manual mapping6// ...7}89// schema.ts10import { makeSchema, objectType } from 'nexus'11import * as path from 'path'1213const User = objectType({14 name: 'User',15 // ^-------------- Name of your GraphQL Object type which will be matched against16 definition(t) {17 // ...18 }19})2021const schema = makeSchema({22 sourceTypes: {23 modules: [{24 module: path.join(__dirname, 'db.ts'),25 // ^---------------- Path to the file containing your Source Types26 alias: 'db',27 // ^----------------- Name used as alias for Nexus to import your Source Types.28 // eg: import * as <source> from <alias>29 }],30 mapping: {31 User: 'db.SomeRandomName'32 // ^-----^------^---------- GraphQL type name33 // |------|---------- Alias of the source where the Source Type is defined34 // |---------- Source Type name to use for the `User` GraphQL type name35 }36 }37})
However, if none of your Source Types names can match your GraphQL Object type names using a RegEx, we suggest you to use the local Source Types configuration.
Debugging Source Types matching
Nexus will not output any errors if your Source Types aren't found.
That being said, if you happen to have some Source Types that are not picked up by Nexus, you can enable the debug
flag in the sourceTypes
configuration.
Nexus will output debug logs, giving your detailed information about which Source Types were found, which were not, and why.
1import { makeSchema } from 'nexus'23const schema = makeSchema({4 // ...5 sourceTypes: {6 // ...7 debug: true8 }9})
Locally configure Source Types
We typically see two use-cases for using the local configuration of Source Types:
- When your Source Type names can't be matched against your GraphQL Object type names
- When you simply prefer keeping your Source Types next to your Object type definition
To do so, you can use the sourceType
property as explained below:
1import { objectType, queryType } from 'nexus'23export interface MyDBUser {4 // | ^-------------------- Create your Source Type5 // ^-------------------------------- Export your Source Type (required)6 firstName: string7 lastName: string8 birthDate: number9}1011export const User = objectType({12 name: 'User',13 sourceType: {14 module: __filename,15 // ^---------------------- Tell Nexus where the Source Type is.16 export: 'MyDBUser'17 // ^---------------------- Tell Nexus the name of the exported Source Type.18 },19 definition(t) {20 t.string('fullName', {21 resolve(user) {22 // ^----------------------- as: MyDBUser23 return user.firstName + ' ' + user.fullName24 },25 })26 t.int('age', {27 resolve(user) {28 // ^------------------------ as: MyDBUser29 return yearsSinceUnixTimestamp(user.birthDate)30 },31 })32 },33})3435export const Query = queryType({36 definition(t) {37 t.list.field('users', {38 type: 'User',39 resolve(_source, args, ctx) {40 // ^------- return as: MyDBUser[]41 return ctx.db.user.getMany()42 },43 })44 },45})