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 fullName
4 }
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:

  1. Client sends a query

  2. The field resolver for Query.user runs. Remember Query fields (along with Subscription, Mutation) are entrypoints, also known as root fields because they are on Root Types.

  3. 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` type
    2t.field('user', {
    3 resolve(source, args, ctx, info) {
    4 // ^---- as: User (Source Type)
    5 return { firstname: 'Foo', lastname: 'Bar' } // fake db record
    6 }
    7})
  4. 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. Its source argument is the user model data fetched in step 3. This is the Source Type data for the GraphQL User object.

    1// Field of `User` type
    2t.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'
2
3export 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})
16
17export interface UserSourceType {
18 firstname: string
19 lastname: string
20}
21
22export 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.lastname
32 },
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: string
3 name: string
4}
5
6export class Post {
7 id: string
8 title: string
9}
10
11export type CommentModel = {
12 id: string
13 body: string
14}
15
16export enum Color {
17 RED
18 GREEN
19 BLUE
20}

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.ts
2
3export interface User {
4// ^-------------^---------- Export your Source Type (required)
5// |---------- Name of your Source Type which will be extracted by Nexus
6 firstName: string
7 lastName: string
8}
9
10export interface Post {
11// ^-------------^---------- Export your Source Type (required)
12// |---------- Name of your Source Type which will be extracted by Nexus
13// ...
14}
15
16export interface Comment {
17// ^-------------^---------- Export your Source Type (required)
18// |---------- Name of your Source Type which will be extracted by Nexus
19// ...
20}
21
22// ./schema.ts
23
24import { makeSchema, objectType, queryType } from 'nexus'
25import * as path from 'path'
26
27const User = objectType({
28 name: 'User',
29 // ^-------------- Name of your GraphQL Object type which will be matched against
30 definition(t) {
31 t.string('fullName', {
32 resolve(user) {
33 // ^------------------------ as: User
34 return user.firstName + ' ' + user.lastName
35 }
36 })
37 }
38})
39
40const 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})
50
51const 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 Types
57 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 sources
61 }]
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 type User
  • Source Type class PostModel {} -> GraphQL Object type Post
  • Source Type class CommentModel {} -> GraphQL Object type Comment

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.ts
2
3export class UserModel {
4// ^-------------^---------- Export your Source Type (required)
5// |---------- Name of your Source Type which will be extracted by Nexus
6 firstName: string
7 lastName: string
8}
9
10export class PostModel {
11// ^-------------^---------- Export your Source Type (required)
12// |---------- Name of your Source Type which will be extracted by Nexus
13// ...
14}
15
16export class CommentModel {
17// ^-------------^---------- Export your Source Type (required)
18// |---------- Name of your Source Type which will be extracted by Nexus
19// ...
20}
21
22// schema.ts
23import { makeSchema, objectType, queryType } from 'nexus'
24import * as path from 'path'
25
26const User = objectType({
27 name: 'User',
28 // ^-------------- Name of your GraphQL Object type which will be matched against
29 definition(t) {
30 t.string('fullName', {
31 resolve(user) {
32 // ^------------------------ as: UserModel
33 return user.firstName + ' ' + user.lastName
34 }
35 })
36 }
37})
38
39const 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})
49
50const 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 Types
56 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 Types
60 // |---------------- Default regex used to match your GraphQL types against your Source Types
61 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 behavior
10 new RegExp(`(?:interface|type|class|enum)\\s+(${type.name}Model)\\W`, 'g'),
11 // new RegExp(...), -> Add more regexes if you have even more convention
12 ]
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.ts
2
3export class SomeRandomName {
4// ^-------------^------- Export your Source Type (required)
5// |------- Name of your Source Type that you'll use for the manual mapping
6// ...
7}
8
9// schema.ts
10import { makeSchema, objectType } from 'nexus'
11import * as path from 'path'
12
13const User = objectType({
14 name: 'User',
15 // ^-------------- Name of your GraphQL Object type which will be matched against
16 definition(t) {
17 // ...
18 }
19})
20
21const schema = makeSchema({
22 sourceTypes: {
23 modules: [{
24 module: path.join(__dirname, 'db.ts'),
25 // ^---------------- Path to the file containing your Source Types
26 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 name
33 // |------|---------- Alias of the source where the Source Type is defined
34 // |---------- Source Type name to use for the `User` GraphQL type name
35 }
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'
2
3const schema = makeSchema({
4 // ...
5 sourceTypes: {
6 // ...
7 debug: true
8 }
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'
2
3export interface MyDBUser {
4 // | ^-------------------- Create your Source Type
5 // ^-------------------------------- Export your Source Type (required)
6 firstName: string
7 lastName: string
8 birthDate: number
9}
10
11export 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: MyDBUser
23 return user.firstName + ' ' + user.fullName
24 },
25 })
26 t.int('age', {
27 resolve(user) {
28 // ^------------------------ as: MyDBUser
29 return yearsSinceUnixTimestamp(user.birthDate)
30 },
31 })
32 },
33})
34
35export 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})
Edit this page on Github