🚀 Boiler
The Production-ready GraphQL boilerplate.
Translations
Pre-configured
- Yarn 2
- TypeScript
- Webpack
- Babel
@babel/preset-typescript
@babel/preset-env
(Node.js 14)
- GraphQL JIT Executor
- Sentry Apollo Plugin
- Winston Logger
- Node Hot Loader
Pre-installed
GraphQL
- Apollo Server
- GraphQL Tools
- GraphQL Code Generator
Back-end
- MongoDB
- Mongoose
- Agenda.js
Front-end
- Next.js
- React.js
- Relay
Pre-written
- GraphQL Operation Executor
- GraphQL Connection Helper (
@internal/graphql-connection-resolver
)
Getting Started
# Run MongoDB instance by backgrond
$ docker-compose up -d
# Install dependencies
$ yarn
# Copy .env.example to .env
$ cp .env.example .env
# Start development server
$ yarn dev
Development
# Generate TypeScript typing from GraphQL schema declaration
$ yarn codegen
# Generate relay artifacts
$ yarn relay
Build
# Build server
$ yarn build
Deployment
Use docker. Dockerfile
is already built-in.
Folder Structure and Architecture Principles
/graphql
1. Contains the content corresponding to the GraphQL API layer.
- Schema declarations and the resolvers
- Apollo server Options
/graphql/schema/[model]
1-a. Write the schema declaration and resolver implementation
⭐️ Principle 1. Separate code locations by domain (Seperation of concern)
Schema declarations and resolvers are organized by domain (object model) and included in the domain name folder.
⭐️ Principle 2. Separate the API layer and the persistent layer
The domain of the GraphQL layer and the persistent layer (usually it means a table/collection of a database) should be separated by location.
Database tables/collections are written in
/src/models
and mapped in/codegen.yml
before use.
⭐️ Principle 3. Include Description in all GraphQL schema declarations
The behavior of the code can be fully understood through the context around it, but the API layer at the end of the code or the persistent layer at the inner end of the code must be commented for full understanding.
/graphql/schema/resolvers.ts
1-b. Collect resolvers exported from each domain folder
⭐️ Principle 4. Minimize automation
Anything related to the principle of operation is never automated. In order to collaborate with colleagues, all code actions must be written by hand.
/graphql/context.ts
1-c. Write the context type declaration and context generator
/graphql/server.ts
1-d. Create an Apollo Server. You can modify the options of Apollo Server. The GraphQL JIT Executor is integrated here.
/lib
2. Create an internal library to be used inside the application
⭐️ Principle 5. Extract the technical part of the logic.
Simple separation is not the refactoring
. Separate the technical parts of your logic, create an internal library, and keep the code so that only the business logic remains in the application logic.
⭐️ Principle 6. Design the internal library like an NPM package.
The internal library should not import code from a folder other than /lib
. Be sure to design so that the necessary dependencies can be injected externally in any way you want. (this is called dependency injection)
For this, I have set up aliases in webpack and typescript. You can use the internal library like this:
import * as F from '@internal/f'
/constants
3. Contains environment variables injected into the server, or constants computed through environment variables.
⭐️ Principle 7. Constants must be commented
As with the Principle 3
, specific comments are required to accurately understand the environment variables that exist at the end of the server.
/models
4. Contains content related to the persistent layer. (Here I mean MongoDB and Mongoose declarations)
Soft delete and timestamp plugins have been set up for immediate use in production. Delete it if you don't need it.
⭐️ Principle 8. Models must be commented
As with the Principle 3
, specific comments are required to accurately understand the models.
/restapi
5. Here are the basic REST API settings for API interworking with the other internal service.
⭐️ Principle 9. Application logic is handled only at the GraphQL layer.
REST API is difficult to separate code by domain. (That's also the main reason to use GraphQL) So, with the executeOperation
function (in /src/graphql/executeOperation
), the REST API is only used to alias the API.
/agenda
6. Write asynchronous worker logic for cron and other tasks here. See Agenda.js.
Processing large amounts of data (JSON.parse) can be a blocking factor in the Node.js server. Therefore, if there is a large amount of data processing inside the execution of asynchronous logic, server performance may be affected. At this time, it is recommended to separate the worker instance from the server instance using environment variables. (However, most startups do not process large amounts of data, so it is recommended to start small with one instance like this boilerplate.)
/client
7. Contains the Next.js client implementation.
⭐️ Principle 10. Follows relay
Follow the pattern that Relay enforces. Not only does it increase the reuse of React components, it helps design good schemas, and actually brings good performance.
How to keep the server performant?
- GraphQL query parsing is blocking operation in Node.js. Cache the parsed results or just use the JIT Executor. (Included in this boilerplate.)
- Relay clients increase the size of a single query. Use Persisted Queries to reduce network costs. (I didn't include it in this boilerplate, as it requires implementation in the persistent layer.)
- I recommend using Apollo Studio and Datadog Trace.
Keep 50~100ms based on p99
To-do
WIP