The electric FeathersJS and Apollo Server: The start
It’s the middle of winter, and what can you do other than fantasize about a warm beach and humidity? If you’re like me, you’ll spend your time trying to learn a new skill. GraphQL had been on my radar for a while, and I spent enough time reading tutorials, but like most things, it just doesn’t stick until I’m engaged in creating something. So I scrapped the lessons and started to build something new.
For starters, I need to give props to Scott Swarthout for introducing me to using Apollo Server and Feathers JS together. His article “The Eagle has Landed” laid a solid foundation for using these two stacks in harmony, but it left out a few details that would be quite helpful to a beginner.
In that article, you’ll learn one of the old standards, of how to build a blog. Like I said before, I need to be engaged so after reading this article I jumped right into building an app that would be useful to me. An app should solve a problem.
My problem was I don’t have a clue about my home’s electrical system. If I wanted to replace a light fixture or an outlet I can wander down to the basement, open the circuit breaker box, and blankly stare at 20 unmarked switches. Ohh fun, is it time to break out the flashlight and off turn every breaker before your feel safe to work? Even if a previous own named the circuits, does it match reality? My house had many renovations throughout its life of 130 years. I can attest that someone, was ‘creative’, it’s not an ideal schematic of one breaker powering one room.
The solution is to build an app that will visually map out your electrical system. This article will cover my first attempt at implementing the API server of such an app in GraphQL.
Apollo GraphQL Server: http://dev.apollodata.com/tools/graphql-server/
Feathers JS: http://feathersjs.com/
So it begins, Not another Blog Tutorial
First, install Node.js and NPM. Google that if you’ve never used Node before. For this article, I’m using node v7.4.0 and npm 4.0.5. , a bit ahead of the latest LTS version.
Once you have node and npm installed, enter the following commands into your command line to install Feathers globally. That last command is the Feathers project generator.
$ npm install -g feathers-cli
$ mkdir electrical-system
$ cd electrical-system/
$ feathers generate
Answer the wizard’s questions:
? Project name electrical-system
? Description GraphQL Server representing a home electrical system
? What type of API are you making? (Press <space> to select, <a> to toggle all, <i> to inverse selection)REST, Realtime via Socket.io
? CORS configuration Enabled for all domains
? What database do you primarily want to use? MongoDB
? What authentication providers would you like to support?local
Install Apollo Server requirements
npm install — save graphql-server-express graphql-tools graphql bcrypt-as-promised jsonwebtoken request request-promise
Install Babel and Nodemon
npm install — save babel-cli babel-preset-es2015 nodemon
Configure Babel by adding this to the root of your package.json file.
"babel": {"presets": ["es2015"]},
Also in your package.json change the start script to use babel-node instead of node because we want babel to compile ES2015 syntax into compatible Javascript.
“start”: “babel-node src/”,
Add a dev script that uses nodemon, so you don’t need to keep restarting your script as you are developing.
“dev”: “nodemon src/ — exec babel-node — presets es2015”,
Now use feathers to generate the graphql service, IE /graphql endpoint. Feathers alone allows you to create RESTful endpoints, so this is the service that will interface the Apollo graphqlExpress server with the standard Feathers REST services you create.
$ feathers generate service
? What do you want to call your service? graphql
? What type of service do you need? generic
? Does your service require users to be authenticated? No
Now open src/services/graphql/index.js and replace the generated service template with the following script.
'use strict';const hooks = require('./hooks');
import { graphqlExpress, graphiqlExpress } from 'graphql-server-express';
import { makeExecutableSchema, addMockFunctionsToSchema } from 'graphql-tools';
import Resolvers from './resolvers';
import Schema from './schema';module.exports = function () {
const app = this;const executableSchema = makeExecutableSchema({
typeDefs: Schema,
resolvers: Resolvers.call(app)
});// Initialize our service with any options it requires
app.use('/graphql', graphqlExpress((req) => {
let {token, provider} = req.feathers;
return {
schema: executableSchema,
context: {
token,
provider
}
}
}));app.use('/graphiql', graphiqlExpress({
endpointURL: '/graphql',
}))};
This script provides the Apollo server GraphQL query endpoint and GraphiQL user interface for testing GraphQL queries. Feathers manages routing and request data. The object variable “req.feathers.token” stores the authorization token. Here it’s passed along to our GraphQL resolvers in the context. It is crucial to the Feathers integration for the context to have the “token” and “provider” keys here because they are passed on to the feathers hooks middleware. Without this, your secured Feathers RESTful services will be unsecured when accessed through the /graphql endpoint.
Now let’s start working on the schema and resolvers, make two new files in the graphql folder named schema.js and resolvers.js.
If this is your introduction to Apollo graphQL server, I’ll provide a very basic explanation.
First, the schema is a conceptual outline of the data types you wish to make available, the queries to return data from the server, and mutations used to update data. Both “queries” and “mutations” are comparable to functions. You provide input parameters and the query returns data. The important distinction between “queries” and “mutations” is not that “queries” are for reading data, and “mutations” are for writing data, because either can do both. The distinction is that “mutations” are executed in a FIFO order, so one is committed before the next begins.
Last, the resolver mirrors the schema in structure, but it is no longer conceptual. Now, everything is assigned database queries, or calls to external API’s. All the heavy lifting happens in the resolver, but no worries, Feathers helps make this a succinct as possible.
Now that we’ve gone over the concepts let’s design the schema for our electrical system. For starters read this outline of an electrical system to start forming your mental map.
A building’s electrical system typically consists of the “main”, the wires entering the building. A typical home has 100 to 200 amp service. This main runs into the home’s circuit “panel”. Typically this panel is in the basement.
That panel divides the main into smaller circuits. The “breaker” protects each circuit from overload. It will stop power when there is more current is drawn than what the wire can support. That wire connected runs through the home to various loads, loads being the lights and outlets. The breaker must trigger before the current drawn exceeds the weakest point in wire to prevent fires.
The schema will start with “back quotes”; this is a template literal. TLDR: just do it. More: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals
const typeDefinitions = ``;
export default [typeDefinitions]
From there let’s start by defining our models, these correspond to our tables or collections, depending on your database’s lingo. That’s what’s nice about Feathers, is it has connectors for many databases and a single query syntax for all of them. ( Memory, MongoDB, MySQL, MariaDB, NeDB, PostgreSQL, SQLite, SQL Server ) That means if you want to switch a service from MongoDB to MySQL you’ll just need a new service-model.js file for the database. And in the case of MongoDB to MySql, change the indexes, “_id” to “id” in your queries.
For a reference on the GraphQL schema syntax visit here: https://wehavefaces.net/graphql-shorthand-notation-cheatsheet-17cd715861b6#.p1mb6qfp4
const typeDefinitions = `enum LoadType {
FIXTURE
OUTLET
}type User {
_id: String! # Indicative of MongoDB, use id if you'd like to use SQL
createdAt: String
firstName: String
lastName: String
username: String!
buildings: [Building] # User has many Buildings
}type Building {
_id: String!
createdAt: String
name: String!
address1: String
address2: String
city: String
state: String
panels: [Panel] # Building has many Panels, even though there's probably only one.
rooms: [Room] # Building has many Rooms
}type Panel {
_id: String!
createdAt: String
name: String!
rating: Int!
slots: Int!
image: Image # Panel has one Image
breakers: [Breaker] # Panel has many Breakers
}type Room {
_id: String!
createdAt: String
label: String!
Loads: [Load]
}type Image {
_id: String!
createdAt: String
url: String!
}type Breaker {
_id: String!
createdAt: String
label: String!
description: String
rating: Int!
loads: [Load] # Breaker has many Loads , IE lights and outlets
}type Load {
_id: String!
createdAt: String
label: String!
type: String!
image: Image # Load has one Image
switches: [Toggle] # Load may have many Toggles, single or 2 way
}type Toggle { # Can't call this switch
_id: String!
createdAt: String
label: String!
image: Image # Toggle has one Image
}
`;export default [typeDefinitions]
Now before we get to the nifty parts of GraphQL, the queries and resolvers, again let’s use feathers to generate the services and models that we’ve defined.
$ feathers generate service? What do you want to call your service? building
? What type of service do you need? database
? For which database? MongoDB
? Does your service require users to be authenticated? Yes
Do this for each of your collections except for User since it already exists.
building, panel, room, image, breaker, load, toggle
Finally, let’s make the viewer service for accessing the current user.
$ feathers generate service
? What do you want to call your service? viewer
? What type of service do you need? generic
? Does your service require users to be authenticated? Yes
One of the perils of auto generated code is the load order of the services. Currently, the graphql service will not have access to the database library, mongoose in this case. Open /src/services/index.js and move the graphql service to the end.
app.configure(graphql);
So change the file from:
module.exports = function() {
const app = this;mongoose.connect(app.get('mongodb'));
mongoose.Promise = global.Promise;app.configure(authentication);
app.configure(user);
app.configure(graphql);
app.configure(building);
app.configure(panel);
app.configure(room);
app.configure(image);
app.configure(breaker);
app.configure(load);
app.configure(toggle);
app.configure(viewer);
};
To this:
module.exports = function() {
const app = this;mongoose.connect(app.get('mongodb'));
mongoose.Promise = global.Promise;app.configure(authentication);
app.configure(user);
app.configure(building);
app.configure(panel);
app.configure(room);
app.configure(image);
app.configure(breaker);
app.configure(load);
app.configure(toggle);
app.configure(viewer);
app.configure(graphql);
};
Now let’s go in and define each mongoose model with the needed fields. Go to src/services/building and open building-model.js and index.js. In index.js comment out or remove the “paginate” key from the “const” options, the Feathers / Apollo integration fails if it’s there. It should end up like:
const options = {
Model: building
};
I haven’t tried pagination with Feathers and Apollo, Apollo itself has an offset and cursor based pagination, so that’s something TBD later.
Inside building-model.js you’ll start out with the buildingSchema. Every one of them will start like this. Define all the fields except _id. Remember that in our GraphQL schema, ! indicates required.
Mongoose reference:
const buildingSchema = new Schema({
text: { type: String, required: true },
createdAt: { type: Date, 'default': Date.now },
updatedAt: { type: Date, 'default': Date.now }
});
I’m ambivalent about the timestamps at the moment, but they could be useful down the road.
Building Schema
const buildingSchema = new Schema({
createdAt: { type: Date, 'default': Date.now },
updatedAt: { type: Date, 'default': Date.now },
userId: { type: String, required: true },
name: { type: String, required: true },
address1: { type: String, required: false },
address2: { type: String, required: false },
city: { type: String, required: false },
state: { type: String, required: false }
});
Panel Schema
const panelSchema = new Schema({
createdAt: { type: Date, 'default': Date.now },
updatedAt: { type: Date, 'default': Date.now },
buildingId: { type: String, required: true },
name: { type: String, required: true },
rating: { type: Number, required: true },
slots: { type: Number, required: true },
});
Room Schema
const roomSchema = new Schema({
createdAt: { type: Date, 'default': Date.now },
updatedAt: { type: Date, 'default': Date.now },
buildingId: { type: String, required: true },
label: { type: String, required: true }
});
Image Schema
Here I’m going to try something unusual. Maybe it won’t work, but I don’t want multiple Image collections for Panel, Load, and Toggle, and I have no idea about how polymorphic relationships applying here. I’m sure I could query an objectId and objectName pattern in the queries and resolvers as well. TBD, it’s a learning exercise after all.
const imageSchema = new Schema({
createdAt: { type: Date, 'default': Date.now },
updatedAt: { type: Date, 'default': Date.now },
panelId: { type: String, required: false },
loadId: { type: String, required: false },
toggleId: { type: String, required: false },
url: { type: String, required: true }
});
Breaker
const breakerSchema = new Schema({
createdAt: { type: Date, 'default': Date.now },
updatedAt: { type: Date, 'default': Date.now },
panelId: { type: String, required: true },
label: { type: String, required: true },
description: { type: String, required: false },
rating: { type: Number, required: true }
});
Load
const loadSchema = new Schema({
createdAt: { type: Date, 'default': Date.now },
updatedAt: { type: Date, 'default': Date.now },
breakerId: { type: String, required: true },
roomId: { type: String, required: true },
label: { type: String, required: true },
type: { type: String, required: true }
});
Toggle
const toggleSchema = new Schema({
createdAt: { type: Date, 'default': Date.now },
updatedAt: { type: Date, 'default': Date.now },
loadId: { type: String, required: true },
text: { type: String, required: true }
});
User
const userSchema = new Schema({
createdAt: { type: Date, 'default': Date.now },
updatedAt: { type: Date, 'default': Date.now },
username: {type: String, required: true, unique: true},
firstName: { type: String},
lastName: { type: String},
password: { type: String, required: true }
});
For the User service, we also need to update the “before” hooks, so the data is accessible for logging in. As you can imagine, Find and Get methods are necessary for checking your login. Open access to user accounts looks like a security concern the resolve, but we’ll look at that later.
exports.before = {
all: [],
find: [
// auth.verifyToken(),
// auth.populateUser(),
// auth.restrictToAuthenticated()
],
get: [
// auth.verifyToken(),
// auth.populateUser(),
// auth.restrictToAuthenticated(),
// auth.restrictToOwner({ ownerField: '_id' })
],
... snipit
Setup the user viewer, IE me endpoint.
class Service {
constructor(options) {
this.options = options || {};
}find(params) {
return Promise.resolve(params.user);
}
}
Add the viewer response hook to remove the password hash from the results.
exports.before = {
all: [
auth.verifyToken(),
auth.populateUser()
// auth.restrictToAuthenticated()
],
find: [],
get: [],
create: [],
update: [],
patch: [],
remove: []
};exports.after = {
all: [hooks.remove('password')],
find: [],
get: [],
create: [],
update: [],
patch: [],
remove: []
};
Now let’s return to the graphql service schema and define the queries and mutations.
Append this after the model definitions.
# the schema allows the following queries:
type RootQuery {
}# this schema allows the following mutations:
type RootMutation {
}# we need to tell the server which types represent the root query
# and root mutation types. We call them RootQuery and RootMutation by convention.
schema {
query: RootQuery
mutation: RootMutation
}
Start with what queries your application needs inside RootQuery.
type RootQuery {
viewer: User
buildings: [Building]
building(_id: String!): Building
rooms(buildingId: String!): [Room]
room(_id: String!): Room
panels(buildingId: String!): [Panel]
panel(_id: String!): Panel
breakers(panelId: String!): [Breaker]
roomLoads(roomId: String!): [Load]
breakerLoads(breakerId: String!): [Load]
toggle(_id: String!): Toggle
}
Let’s start with this RootMutation, but frankly, before I write the whole graphql API I’d like to see if anything works.
type RootMutation {
signUp (
username: String!
password: String!
firstName: String
lastName: String
): UserlogIn (
username: String!
password: String!
): AuthPayloadcreateBuilding (
name: String!
address1: String
address2: String
city: String
state: String
): BuildingupdateBuilding (
_id: String!
name: String!
address1: String
address2: String
city: String
state: String
): Building// deleteBuilding (
// _id: String!
// }createRoom (
label: String!
): RoomupdateRoom (
_id: String!
label: String!
): Room// deleteRoom (
// _id: String!
// ): RoomcreatePanel (
buildingId: String!
name: String!
rating: Int!
slots: Int!
): PanelupdatePanel (
_id: String!
buildingId: String!
name: String!
rating: Int!
slots: Int!
): Panel// deletePanel (
// _id: String!
// ): Panel# more to do, but let’s seed and see if the whole thing hasn't blown up}
So before your server even starts, the resolvers need to be written. Start out with this boilerplate.
// src/services/graphql/resolvers.js
import verifyPassword from './lib/auth';export default function Resolvers() {let app = this;// Define services here
return {
// Models here RootQuery: { },
RootMutation: { }
}
}
Update this section first to include all your services
// Define services here
let Users = app.service('users');
let Buildings = app.service('buildings');
let Panels = app.service('panels');
let Rooms = app.service('rooms');
let Images = app.service('images');
let Breakers = app.service('breakers');
let Loads = app.service('loads');
let Toggles = app.service('toggles');
let Viewer = app.service('viewer');
Ahh, this is a good time to mention that you better be committed because your server won’t start until everything defined in your schema is completed in your resolvers :( … or I just have something wonky at this point :)
“Are you lost, or just not from around here?”
Listen, if you’re still reading this and determined to write an electrical system app that makes it to market before me, please continue on to the finale where we finish the resolvers and add authorization via Feathers hooks. If not, go makes some cookies and relax. You might be coding until your eyesight gives out, so go out live a little :)