Search code examples
node.jsexpressasync-awaitgraphqlapollo-server

GraphQL path inaccessible after invoking notFoundHandler() in Node.js application


I'm encountering an issue integrating GraphQL into my Node.js application. Everything worked initially, but after adding a notFoundHandler(), the GraphQL endpoint at localhost:3000/graphql is no longer accessible.

Here's my understanding of the problem:

  • The server.start() function for GraphQL is asynchronous.
  • The application's constructor cannot be async.
  • This leads to notFoundHandler() being applied before app.use('/graphql', cors(), expressMiddleware(server));.
  • Consequently, requests to /graphql are incorrectly routed to the notFoundHandler().

I'm seeking solutions to effectively handle this conflict.

Specific details about my application:

  • Node.js version: 20.10.0
  • GraphQL library: @apollo/server
  • Server framework: Express

I've considered potential approaches:

  • invoking notFoundHandler(app) inside setGraphQL method just after app.use('/graphql',cors(),expressMiddleware(server));

I'm open to any suggestions or insights on:

  • Alternative ways to start the GraphQL server within the constructor constraints.
  • Best practices for handling asynchronous operations and middleware order in this scenario.
  • Potential configuration adjustments or library-specific workarounds.

Any guidance would be greatly appreciated!

To provide a clearer picture of the code structure, I'm sharing the following excerpts:

package.json

{
  "name": "gql4",
  "version": "1.0.0",
  "description": "",
  "main": "www.mjs",
  "scripts": {
    "start": "nodemon www.mjs"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@apollo/server": "^4.10.0",
    "cors": "^2.8.5",
    "express": "^4.18.2",
    "nodemon": "^3.0.2"
  }
}

www.mjs

import {Application} from './app.mjs'
new Application();

schema.js

const {GraphQLObjectType,GraphQLSchema, GraphQLInt, GraphQLString} = require('graphql');

const RootQuery = new GraphQLObjectType({
  name: 'RootQuery',
  fields: {
    blogs : {
        type : new GraphQLObjectType({
            name: "blogsType",
            fields : {
                id: {type: GraphQLInt},
                title: {type: GraphQLString},
                text: {type: GraphQLString}
            }
        }),
        resolve: ()=>{
            return {
                id: 1,
                title: "title of blog",
                text: 'text of blog',
            }
        }
      }
    }
});


const graphQlSchema = new GraphQLSchema({
  query: RootQuery
});


module.exports = {
  graphQlSchema
};

notFound.handler.js

module.exports = function notFoundHandler(app){
    app.use((req,res,next)=>{
        res.status(404).json({
            message: "Not Found Route"
        })
    })
}

app.mjs

import express from 'express';
import cors from 'cors';
import { ApolloServer } from '@apollo/server';
import { expressMiddleware } from '@apollo/server/express4';
import notFoundHandler from './notFound.handler.js';
import  {graphQlSchema} from './schema.js';

const app = express();


class Application{
    constructor(){
        this.setExpress();
        this.setConfigs();
        this.setRoutes();
        this.setGraphQL();
        notFoundHandler(app);
    }

    setExpress(){
        const PORT = 3020;
        app.listen(PORT,()=>console.log(`Server is running on port ${PORT}`));
    }

    setConfigs(){
        app.use(express.json());
        app.use(express.urlencoded({extended:true}))
        app.use(cors())
    }

    setRoutes(){
        app.get('/',(req,res)=>res.json("Welcome"))
    }

    async setGraphQL(){
        const server = new ApolloServer({
            schema: graphQlSchema
        })

        await server.start();
        app.use('/graphql',cors(),expressMiddleware(server));
    }
 }
export { Application }

as first solution, I called notFoundHandler(app) inside setGraphQL method just after app.use('/graphql',cors(),expressMiddleware(server));

It works well, but I think it`s not the best practice.

async setGraphQL(){
        const server = new ApolloServer({
            schema: graphQlSchema
        })

        await server.start();
        app.use('/graphql',cors(),expressMiddleware(server));
        notFoundHandler(app);
    }

Solution

  • After hours I found 2 another ways to settle down this issue.

    First Solution:

    app.mjs

    constructor(){
        this.setExpress();
        this.setConfigs();
        this.setRoutes();        
        (async()=>{
            await this.setGraphQL();
            this.setErrorHandler(app);
        })()
    }
    

    (async()=>{...})(): It defines an anonymous asynchronous function using an arrow function syntax (()=>{}). The function is immediately invoked by adding () at the end.

    await this.setGraphQL();: Inside the anonymous asynchronous function, it awaits the completion of a method called setGraphQL() on the current object (this). This method likely sets up GraphQL functionality for the server.

    this.setErrorHandler(app);: Inside the anonymous asynchronous function, after setGraphQL() completes, it calls a method called setErrorHandler() on the current object (this) and passes an app argument. This method probably sets up an error handler for the server.

    Second Solution:

    In second solution I modified notFoundHandler() function.

    notFound.handler.js

    module.exports = function notFoundHandler(app){
    app.use((req,res,next)=>{
        if(req.path == '/graphql'){
            next();
        }else{
            res.status(404).json({
                message: "Not Found Route"
            })
        }
    })
    

    }

    In notFoundHandler() there is an if statement: if (req.path == '/graphql') { ... }. This checks if the requested path is equal to '/graphql'.

    If the condition in the if statement is true the next() function is called. This function is used to pass control to the next middleware function in the application's request-response cycle.

    If the condition in the if statement is false the else block is executed.

    In the else block, res.status(404) is used to set the HTTP response status code to 404 (Not Found).