The schema contains the information to define all requests that the client can request from an instance of Apollo Server along with the resolvers necessary to route the requests to retrieve data. For most applications, the schema can be placed in a single file along side the resolvers. Many production servers contain a typeDefs string of over a thousand lines. For applications with multiple teams or product domains, this section describes an example application and methods for organizing types and resolvers to make a large instance more modular. The separation between types should follow real-world domains, for example movies vs books, rather than the backend organization. To facilitate this ability to organize by real-world domain, common practice is to create a data model layer that enables resolvers across domains to request data from a common interface. Additionally many domains can share types, such as a user profile. In addition to breaking large schemas apart, GraphQL enables schemas to include documentation inline that is viewable in GraphiQL.
const server = new ApolloServer({ typeDefs, resolvers });
server.listen().then(({ url }) => {
console.log(`🚀 Server ready at ${url}`)
});
```
For small to medium applications, collocating all type definition in one string and resolvers in one object is ideal, since central storage reduces complexity. Eventually for larger applications and teams, defining types and resolvers in separate files and combining them is ideal. The next section describes how Apollo Server enables this separation.
<h2id="modularizing-types">Modularizing the schema types</h2>
When schemas get large, we can start to define types in different files and import them to create the complete schema. We accomplish this by importing and exporting schema strings, combining them into arrays as necessary.
```js
// comment.js
const Comment = `
type Comment {
id: Int!
message: String
author: String
}
`;
export default Comment;
```
```js
// post.js
const Comment = require('./comment');
const Post = `
type Post {
id: Int!
title: String
content: String
author: String
comments: [Comment]
}
`;
// we export Post and all types it depends on
// in order to make sure we don't forget to include
// a dependency
export default [Post, Comment];
```
```js
// schema.js
const Post = require('./post.js');
const RootQuery = `
type RootQuery {
post(id: Int!): Post
}
`;
const SchemaDefinition = `
schema {
query: RootQuery
}
`;
const server = new ApolloServer({
//we may destructure Post if supported by our Node version
<h3id="sharing-modular-types">Sharing types across domains</h3>
Schemas often contain circular dependencies or a shared type that has been hoisted to be referenced in separate files. When exporting array of schema strings with circular dependencies, the array can be wrapped in a function. The Apollo Server will only include each type definition once, even if it is imported multiple times by different types.Preventing deduplication of type definitions means that domains can be self contained and fully functional regardless of how they are combined.
```js
// author.js
const Book = require('./book');
const Author = `
type Author {
id: Int!
firstName: String
lastName: String
books: [Book]
}
`;
// we export Author and all types it depends on
// in order to make sure we don't forget to include
// a dependency and we wrap it in a function
// to avoid strings deduplication
export default () => [Author, Book];
```
```js
// book.js
const Author = require('./author');
const Book = `
type Book {
title: String
author: Author
}
`;
export default () => [Book, Author];
```
```js
// schema.js
const Author = require('./author.js');
const RootQuery = `
type RootQuery {
author(id: Int!): Author
}
`;
const SchemaDefinition = `
schema {
query: RootQuery
}
`;
const server = new ApolloServer({
//we may destructure Post if supported by our Node version
We can accomplish the same modularity with resolvers by passing around multiple resolver objects and combining them together with Lodash's `merge` or other equivalent:
The `extend` keyword provides the ability to add fields to existing types. Using `extend` is particularly useful in avoiding a large list of fields on root Queries and Mutations.
```js
const barTypeDefs = `
type Query {
bars: [Bar]!
}
type Bar {
id
}
`;
const fooTypeDefs = `
type Foo {
id: String!
}
extend type Query {
foos: [Foo]!
}
`
const typeDefs = [barTypeDefs, fooTypeDefs]
```
<h2id="descriptions">Documenting your Schema</h2>
GraphiQL has built-in support for displaying docstrings with markdown syntax. This schema includes docstrings for types, fields and arguments.
```graphql
"""
Description for the type
"""
type MyObjectType {
"""
Description for field
Supports multi-line description
"""
myField: String!
otherField(
"""
Description for argument
"""
arg: Int
)
}
```
<h2id="api">API</h2>
TODO point at graphql-tools `makeExecutableSchema` api