GraphQL is an specification for an Query language and API engine with implementations in many different languages. Specification clearly abstracts from the underlying database solutions, leaving developers to work with any source of the data including REST API or different databases. In this blog post we are going to draft options for adopting GraphQL API in new or existing projects — most particularly focusing on the database layer.
GraphQL — How to adopt it?
Developers can adopt GraphQL API in many different ways:
1) API gateway for microservices exposing GraphQL or REST API
This case is really well documented by Apollo. Adopters can benefit from existing solutions like DataSources that will help them to wrap existing REST API. Additionally Apollo supports Schema Stitching that allows to join multiple GraphQL schemas into single API. Thanks to DataSources and Schema Stitching developers can build GraphQL based microservices architecture where underlying servers can expose. If you are interested in this use case please follow official Apollo blog post about this topic.
2) Standalone server that is exposing underlying database using GraphQL API
Apollo GraphQL server could be added with existing Node.js server running express. When adding Apollo GraphQL libraries server will typically expose one additional endpoint (for example
/graphql ) that will be used by clients to send and retrieve GraphQL data. This use case is widely covered in Apollo GraphQL documentation and blogs and tutorials. After connecting GraphQL libraries developers will need to write resolvers that are going to execute some internal business logic or operate directly on the database. In this blog we are going to focus on the later — when GraphQL resolvers expose directly the data source
Resolvers for your datasource
No matter if you use ORM, SQL builder like Knex.js or having some complex business logic, GraphQL will always have at least two layers of resolvers:
- Type resolvers that are used to fetch the type
- Field resolvers that resolve actual field and can trigger another type resolver in case of relationships
The major misconception when building resolvers is that our type resolvers will need to return exactly what client requested, which could be challenge with working with ORM solutions. When working with Apollo GraphQL autogenerated second level or resolvers are going to deal with this problem by returning exact values for the fields that were requested by clients.
Finding perfect database for GraphQL storage
Could NoSQL database like MongoDB has any advantages over relational databases as datasource for GraphQL? Schemaless databases are tempting to use because we do not need to deal with DDL statements under the hood, however NoSQL database may still require some changes in resolvers (all depends how resolvers are written.
Pros of using NoSQL
- No schema — no need to alter tables when adding new field.
- Simplified relationships management (no constraints etc.)
- Ability to generalize schema (Passing generic JSON Scalar as part of the field.
- Mapping between database and schema can be done using ORM
Cons of using NoSQL
- Lack of enforcement for types and database structure can lead to GraphQL errors when listing data
- When adding new required fields developers will need to provide way to return new value or execute command that will insert default values.
- If there is external source of the data resolvers needs to check if it is going to comply with the provided schema. Inserts from external API can break GraphQL API easily.
Pros of using SQL/Schema based DB
- Enforcement for types and database structure
- Direct mapping between Database schema and GraphQL schema without ORM
Cons of using SQL/Schema based DB
- Changing schema requires user to executes changes in the table — however this could be automated by underlying framework.
- Unclear path for field deletions — it’s not recommended to drop column in existing database etc.
If we are making many changes in your schema and want to avoid dealing with database modifications NoSQL storage solution can be the more suitable aproach for data storage. However when using NoSQL solution developers will need to make sure that new required fields are fetched from the database which can be resolved by inserting data to database or providing default value in resolvers.
Relational databases like Postgres give developers more control over the types and format of the data, by operating on the underlying database model. Any change to the schema needs to be reflected inside database. It’s worth to add that Postgres and other relational databases offer JSON based storage, but it’s not as powerful as any classical NoSQL solution.