10

I want to use morgan's tiny log statements for all of my routes, except graphql endpoints. I'm using express and Apollo 2, and have not been able to get middleware working with express. As the code sample shows, I can install middleware for the entire express app, but I want to limit the scope.

My first attempt was to create an express.router() and pass the router to apolloServer.applyMiddleware, but that doesn't seem to work.

I want to use morgan--but I also want to use express-jwt middleware.

import morgan from 'morgan'
import { mergeSchemas } from 'graphql-tools'
import { ApolloServer } from 'apollo-server-express'

import assessmentSchema from './assessment/schema'
import AssessmentAPI from './assessment/dataSource'

import userSchema from './user/schema'
import UserAPI from './user/dataSource'

/**
 * Installs apollo-server to handle requests under `path`
 * @param {*} app Express instance
 * @param {*} path route path, like '/graphql'
 */
export const createApi = (app, path) => {
  const dataSources = () => ({
    assessmentAPI: new AssessmentAPI({ store: 'intentionally undefined' }),
    userAPI: new UserAPI()
  })

  const schema = mergeSchemas({
    schemas: [assessmentSchema, userSchema]
  })

  morgan.token('graphql-query', req => {
    const { operationName } = req.body
    return `GRAPHQL: Operation Name: ${operationName}`
  })

  // TODO: Add custom logging middleware for GraphQL queries/mutations
  // The next line would add middleware to all of express, but I only want this style of logging for graphQL

  /*** Question is about the following line ***/
  // app.use(morgan(':graphql-query'))

  const apolloServer = new ApolloServer({ schema, dataSources })
  apolloServer.applyMiddleware({ app, path })
}

Thanks!

Jared Dykstra
  • 3,596
  • 1
  • 13
  • 25
  • Added related comment on GitHub: https://github.com/apollographql/apollo-server/issues/1308#issuecomment-444177991 – Jared Dykstra Dec 04 '18 at 17:08
  • I was just trying with express router and it is working. https://gist.github.com/rohitharkhani/45d3111807a7094b73bea71c124c742b. Not sure if this is specific to other module. Can you just double check you have added router in to the app? – Rohit Harkhani Dec 11 '18 at 10:40

4 Answers4

5

There are some 'Hacky' ways to achieve what you desire. You can use express.Route to register middlewares on each route instead, but I think that you may want more specific logs about GraphQL rather than the request in particular.

context()

Available as a callback inside ApolloServer, it receives an object with the request and response.

const myServer =  new ApolloServer({
  schema: ...,
  context:({ req, res }) => { 
    // log here
  }
});

fortmatResponse()

Available as a callback inside ApolloServer, it receives the response and the query.

const server = new Apollo.ApolloServer({
  schema: ...,
  formatResponse: (res, query) => {
    // log here

    // notice you must return the response
    return res;
  },
});

Sources: formatResponse, context

Edit

Another thing you can do is on morgan callback check if the req.path matches with the /graphQL path, and log only in that situation but this is much the same as log an Express.Route with morgan

Carl Manaster
  • 39,912
  • 17
  • 102
  • 155
DobleL
  • 3,808
  • 14
  • 20
  • I agree that these are hooks where code could be executed with each graphql request (either via `context()` before the request, or `formatResponse()` after, but neither are designed to support express middleware. – Jared Dykstra Dec 04 '18 at 15:58
  • What I did do is add `req` to the context object. It seems like the best option I've come up with so far. `context: ({req, res}) => ({req, res})` This makes it possible to access the express req/res from within the graphql resolvers and datasource. – Jared Dykstra Dec 04 '18 at 16:01
  • Yeah, thought about that too, also I've edited the answer adding one more way to do it, but it's the same solution as above i think – DobleL Dec 04 '18 at 16:06
  • 1
    Your approach is the best answer so far--but I was really hoping there was some obvious way to use middlware with Apollo Server (and I just didn't know what it was) Perhaps someone else will contribute another answer--or perhaps its just not possible. – Jared Dykstra Dec 04 '18 at 16:52
  • let me know with what you've ended up – DobleL Dec 04 '18 at 17:38
  • 1
    How would I get next() into the middleware? I tried deconstructing it in the context args, but it comes up undefined. – natep Aug 17 '19 at 17:50
1

With apollo server v2, its really simple to use it only on a single route. apply it as middleware. i.e

const app = require('express')();
const apolloServer = new ApolloServer ({
   typeDefs,
   resolvers
}) 

// then use it on a particular route

apolloServer.applyMiddleware({ app, path: '/specialUrl' });
joanis
  • 10,635
  • 14
  • 30
  • 40
augustino
  • 429
  • 4
  • 4
1
const express = require("express");
const router = express.Router();
const { ApolloServer, gql } = require('apollo-server-express');

const server = new ApolloServer({
    schema: schema,
    introspection: true
}); 

server.applyMiddleware({ app:router });

module.exports = router;
0

There is a npm package GraphQL-Router-Ware

Using this you can add router level middlewares to your resolvers in a much similar way like we do in express.

You can setup your resolver something like this :

import Router from 'graphql-router-ware';
import { checkPermission } from '../helpers/userhalper';
import Controller from '../controllers/page';

const resolvers = {
    Query: {
        singlePage: Router(Controller.singlePage)
    },
    Mutation: {
        createPage: Router(checkPermission,Controller.create),
        updatePage: Router(checkPermission,Controller.update),
    }
}

export default resolvers;

Followed by your middleware :

// ....
export checkPermission=({ ctx },next)=>{

    if(!ctx.user){
        return next(new Error('Not logged in'));
        // or throw new Error('Not logged in')
    }

    // some more permission checks....
    return next();
};
// ....

Hope this was helpful.

Shubham Kumar
  • 538
  • 5
  • 12