node.jsexpressgraphqlapollo

Apollo graphql, how to count the number of arguments of a graphql request and writes log it with morgan library


Here's my problem: I'm trying to count the number of arguments that are received in order to write them in the access logs. I'm using apollo server started from an express, graphql as my API entry point and I'm using morgan library to write access logs.

I try to use the apollo plugins to retrieve information from the graphql request. I can find out from the plugins how many arguments I have, but I can't pass this information to the request context so that the morgan library can retrieve the number of arguments to write it.

I've made a .js file that sums up what I want to do with console.log for debbuging

const cors = require('cors');
const express = require('express');
const morgan = require('morgan');
const { json } = require('body-parser');
const { ApolloServer } = require('@apollo/server');
const { expressMiddleware } = require('@apollo/server/express4');

morgan.token('countID', (req) => {
  console.log('req.countID', req.countID);
  if (req.countID) return req.countID;
  return '-';
});

const logger = morgan('[:date[clf]] ":method :url HTTP/:http-version" :status ":countID"', { stream: process.stdout });

const typeDefs = `#graphql
  type Query {
    hello(id: [ID!]!): String
  }
`;

const resolvers = {
  Query: {
    hello: (parent, args, context, info) => {
      const { id } = args;
      return `id: ${id.length}`;
    },
  },
};

const countIDPlugin = {
  requestDidStart() {
    return {
      executionDidStart() {
        return {
          willResolveField({ info, contextValue: resolverContext }) {
            const args = info?.fieldNodes[0]?.arguments.filter((e) => e.name.value === 'id');
            
            const countID = args[0]?.value?.values?.length

            if (!countID) { 
              return; 
            }
            
            console.log('plugin:', countID)
            resolverContext.countID = countID;
            console.log('resolverContext.countID: ', countID)
          },
        };
      },
    };
  },
};

const server = new ApolloServer({
  typeDefs,
  resolvers,
  introspection: true,
  csrfPrevention: false,
  plugins: [
    countIDPlugin,
  ],
  context: ({ req }) => ({ req }),
});

(async () => {
  const app = express();

  app.use(logger);

  app.use(cors({
    origin: '*',
    allowedHeaders: ['Content-Type', 'x-api-key'],
    method: ['GET', 'POST'],
  }));

  await server.start();

  app.use('/graphql', cors(), json(), expressMiddleware(server, {}));

  app.listen(3000, async () => {
    console.log('Server up')
  });
})();

With this js file, when i start my server and when i do graphql request, there is my log :

Server up
plugin: 3
resolverContext.countID:  3
req.countID undefined
[11/Mar/2024:19:47:00 +0000] "POST /graphql HTTP/1.1" 200 "-"

I don't know if it's the right solution to go through an apollo plugin to do what I want to do, I remain open to other proposals.

Thanks in advance


Solution

  • You can do this by adding context express to the middleware in this way:

    app.use('/graphql', cors(), json(), expressMiddleware(server, {
      context: async ({ req }) => req,
    }));
    

    In your willResolveField function you can then do

    contextValue.res.req.countID = countID;
    

    Here's the code in full and simplified

    const cors = require('cors');
    const express = require('express');
    const morgan = require('morgan');
    const { json } = require('body-parser');
    const { ApolloServer } = require('@apollo/server');
    const { expressMiddleware } = require('@apollo/server/express4');
    
    morgan.token('countID', (req) => (req?.countID || '-'));
    
    const logger = morgan('[:date[clf]] ":method :url HTTP/:http-version" :status ":countID"', { stream: process.stdout });
    
    const typeDefs = `#graphql
      type Query {
        hello(id: [ID!]!): String
      }
    `;
    
    const resolvers = {
      Query: {
        hello: (parent, args, context, info) => (`id: ${context.countID}`),
      },
    };
    
    const countIDPlugin = {
      requestDidStart() {
        return {
          executionDidStart() {
            return {
              willResolveField({ args, contextValue }) {
                const countID = args?.id?.length;
    
                if (!countID) { return; }
    
                contextValue.res.req.countID = countID;
                contextValue.countID = countID;
              },
            };
          },
        };
      },
    };
    
    const server = new ApolloServer({
      typeDefs,
      resolvers,
      introspection: true,
      csrfPrevention: false,
      plugins: [
        countIDPlugin,
      ],
      context: ({ req }) => ({ req }),
    });
    
    (async () => {
      const app = express();
    
      app.use(logger);
    
      app.use(cors({
        origin: '*',
        allowedHeaders: ['Content-Type', 'x-api-key'],
        method: ['GET', 'POST'],
      }));
    
      await server.start();
    
      app.use('/graphql', cors(), json(), expressMiddleware(server, {
        context: async ({ req }) => req,
      }));
    
      app.listen(3000, async () => {
        console.log('Server up')
      });
    })();