How to make GraphQL schema and PostgreSQL Sequelize fields works together — Don’t Repeat Yourself (DRY)
This is a continuation from previous article: How to run GraphQL Nodejs server on PostgreSQL under 30 minutes.
Prerequisite:
- NodeJS 8.14 and above
- Northwind PostgreSQL Dataset (for this demo purpose)
Lets look at the code example from previous post:
./server.js
const typeDefs = gql`
type Query {
order(order_id: ID!): Order
orders: [Order]
}type Order {
order_id: ID
customer_id: String,
employee_id: Int,
order_date: String,
required_date: String,
shipped_date: String,
ship_via: Int,
freight: Float,
ship_name: String,
ship_address: String,
ship_city: String,
ship_region: String,
ship_postal_code: String,
ship_country: String,
}
`;
./models/order.js
const order = sequelize.define('Order', {
order_id: {
type: Sequelize.STRING,
primaryKey: true,
},
customer_id: Sequelize.STRING,
employee_id: Sequelize.INTEGER,
order_date: Sequelize.DATE,
required_date: Sequelize.DATE,
shipped_date: Sequelize.DATE,
ship_via: Sequelize.INTEGER,
freight: Sequelize.REAL,
ship_name: Sequelize.STRING,
ship_address: Sequelize.STRING,
ship_city: Sequelize.STRING,
ship_region: Sequelize.STRING,
ship_postal_code: Sequelize.STRING,
ship_country: Sequelize.STRING,
}, {
schema: 'public',
tableName: 'orders',
timestamps: false,
});
We manage to start the NodeJS server in this way, but there are some coding problems here.
Don’t Repeat Yourself: Why do we have to define schema for same table twice in 2 different form for GraphQL and Sequalize. What if we have 115 table to define and end up 230 definition across the file?
Single Source of Schema Definition: What if there is table change on one definition and developer forgot to change at another definition?
So, we have to map Sequelize fields to GraphQL schema or the other way around.
We are blessed enough with graphql-sequelize module providing a helper call attributeFields to help us map Sequelize database fields to GraphQL schema.
The catch is that we cannot use typeDefs and resolvers for Apollo Server but to construct a full schema object. typeDefs and resolvers is a beautiful interface IMO. If there is no valid advantage provided by schema to the project, using typeDefs and resolvers should be encouraged.
Lets jump right into creating our schema by export object schema definition from model.
// path: ./model/order.jsconst {GraphQLObjectType} = require('graphql');
const {attributeFields} = require('graphql-sequelize');// ... other Sequelize models stuffconst orderObjectType = new GraphQLObjectType({
name: 'Order',
description: 'A Shipping Order',
fields: {...attributeFields(order)}
});module.exports = {
order,
orderObjectType
};
Notice that we use attributeFields to add fields from order to the fields of the GraphQLObjectType.
Then we need to construct a GraphQL Schema in schema.js
// path: ./schema.jsconst {order, orderObjectType} = require('./models/order');
const {
GraphQLSchema,
GraphQLObjectType,
GraphQLList
} = require('graphql');
const {resolver} = require('graphql-sequelize');const queryType = new GraphQLObjectType({
name: 'Query',
fields: {
order: {
type: new GraphQLList(orderObjectType),
resolve: resolver(order),
},
orders: {
type: new GraphQLList(orderObjectType),
resolve: resolver(order,{list: true}),
}
}
});
const schema = new GraphQLSchema({query: queryType});module.exports = {
schema
}
After that we need to import schema into server.js for Apollo Server and a little bit of clean up. Our clean server.js will looks like this:
// path: ./server.jsrequire('./initializeDb.js');
const Koa = require('koa');
const { ApolloServer } = require('apollo-server-koa');
const {schema} = require('./schema');
const server = new ApolloServer({schema});
const app = new Koa();
server.applyMiddleware({ app });
app.listen({ port: 4000 }, () =>
console.log(`🚀 Server ready at http://localhost:4000${server.graphqlPath}`),
);
Lets take some time to admire our final result of moving schema definition out of server.js. Beautiful isn’t it.
Finally we will start the server with
nodemon server.js
Now lets go to http://localhost:4000/graphql and then test our new implementation with
{
orders {
order_id
customer_id
employee_id
order_date
required_date
shipped_date
ship_via
freight
ship_name
ship_address
ship_city
ship_region
ship_postal_code
ship_country
}
}
You should get a similar result from previous code.
We are almost there, we need to reimplement the arguments with order.
Look at our ./schema.js we need to add a ‘args’ field for the query.
We have 2 helper on our side which is “defaultArgs” and “defaultListArgs” which prepare the argument objects for us.
// path: ./schema.jsconst {resolver, defaultListArgs, defaultArgs} = require('graphql-sequelize');const queryType = new GraphQLObjectType({
name: 'Query',
fields: {
order: {
type: orderObjectType,
resolve: resolver(order),
args: defaultArgs(order),
},
orders: {
type: new GraphQLList(orderObjectType),
resolve: resolver(order,{list: true}),
args: defaultListArgs(order),
}
}
});
Now try query with order_id
{
order (order_id: "10248"){
order_id
customer_id
employee_id
order_date
required_date
shipped_date
ship_via
freight
ship_name
ship_address
ship_city
ship_region
ship_postal_code
ship_country
}
}
You should be able to query via the primary key.
And then try query list of order with new parameters
{
orders (limit: 3, offset: 2){
order_id
customer_id
employee_id
order_date
required_date
shipped_date
ship_via
freight
ship_name
ship_address
ship_city
ship_region
ship_postal_code
ship_country
}
}
What we have achieve here with a same outcome and different implementation which allow us to define the fields once in Sequelize rather than defining them two times in GraphQL schema and Sequelize separately.
Caveats of the implementation with graphql-sequelize package:
- Mutation is not included. You might need to write your own mutation resolver function.
- Popularity is low on this package which might deter the chances of continue support from the original author and the community.
Finally, I would like to thanks Mick Hansen for creating this great npm package for the community to explore GraphQL PostgreSQL with ease.