Integrate ApolloServer into an existing Express App

Hello all,

I have created from scratch an Express App by using the Express CLI. Here I have an “www.js” file inside the bin folder and I’ve got an server.js or app.js file.

Inside the app.js / server.js file I have different routes (also a static one). I’ve tried different things, but the error handling seems not to work as expected.

The error handling for the “normal” express server is working, but if I want to have the ApolloServer inside, the standard route “/graphql” returns an error 404.

Maybe I’m wrong with my coding, but how can I have my normal working express app and insert an ApollopServerExpress instance too?

Here is my consolidated server.js file with my own adaptions (both files app.js/server.js and www.js), to make it more readable.

process.on('unhandledRejection', (reason, p) => {
  // eslint-disable-next-line
  console.log('Unhandled Rejection at:', p);
});

process.on('uncaughtException', (exception, next) => {
  // eslint-disable-next-line
  console.log(exception);
  next();
});
const dotenv = require('dotenv');

if (process.env.NODE_ENV === 'development') {
  dotenv.config({ path: './.env' });
}

// const createError = require('http-errors');
const express = require('express');
const { ApolloServer } = require('apollo-server-express');
const path = require('path');
const bodyParser = require('body-parser');
const cookieParser = require('cookie-parser');
const helmet = require('helmet');
const favicon = require('serve-favicon');

const httpStatus = require('http-status-codes');

const fnErr404 = (req, res, next) => {
  const err = new Error('Not Found');
  err.status = httpStatus.NOT_FOUND;
  next(err);
};

const logger = require('./config/logger');

const apolloServer = new ApolloServer({
  modules: [
    require('./GraphQL/hello'),
    require('./GraphQL/games')
  ],
  context: { user: 'test' }
});

const app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'hbs');

app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());

const iconPath = path.join(__dirname, 'public', 'favicon.png');
const options = { maxAge: -1 };
app.use(favicon(iconPath, options));

app.use('/public', express.static(path.join(__dirname, 'public')));

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));

app.use(
  helmet({
    contentSecurityPolicy: false,
    crossOriginEmbedderPolicy: false
  })
);

// cross-origin
const setSpecialHeader = function (req, res, next) {
  res.header('cross-origin-embedder-policy', 'credentialless');
  next();
};

// app routes
const api = require('./routes/index');
const pkg = require('./package.json');
const config = require('./config');

// Register our REST API.
app.use('/api', api);

app.use(setSpecialHeader);
app.use('/', express.static(path.join(__dirname, 'webapp')));

function normalizePort (val) {
  const port = parseInt(val, 10);

  if (Number.isNaN(port)) {
    return val;
  }

  if (port >= 0) {
    return port;
  }
  return false;
};
// function onError (error) {
//   if (error.syscall !== 'listen') {
//     throw error;
//   }
//
//   const bind = typeof port === 'string'
//     ? 'Pipe ' + port
//     : 'Port ' + port;
//
//   switch (error.code) {
//     case 'EACCES':
//       console.error(bind + ' requires elevated privileges');
//       process.exit(1);
//       break;
//     case 'EADDRINUSE':
//       console.error(bind + ' is already in use');
//       process.exit(1);
//       break;
//     default:
//       throw error;
//   }
// };

const port = normalizePort(process.env.PORT);
const AppName = pkg.name;

// not working with ApolloServer
// app.on('error', onError);

// Propagate "404 - not found" error
// not working with ApolloServer
// app.use(fnErr404);

apolloServer.start()
  .then((res, req, next) => {
    apolloServer.applyMiddleware({ app, path: '/graphql' });
    app.listen({ port }, () => {
      logger.debug(`Application: ${AppName} v.${pkg.version} - [BACKEND]`);
      if (config.env === 'development') {
        logger.info(`Server listening on http://localhost:${port}`);
      } else {
        logger.info(`Server listening on port ${port}`);
      }
    });
  })
  .catch((e) => {
    logger.error(e.message);
  });

// not working with ApolloServer
// General error handler
// app.use(function (err, req, res) {
//   const status = err.status || httpStatus.INTERNAL_SERVER_ERROR;
//   const accept = req.get('Accept') || '';
//   const msg = err.message || httpStatus.getStatusText(status);
//
//   // Log error
//   switch (status) {
//     case httpStatus.NOT_FOUND:
//       logger.debug(status, req.url);
//       break;
//
//     default:
//       logger.error(status, err.stack);
//       break;
//   }
//
//   res.status(status);
//
//   if (req.xhr || accept.indexOf('text/html') === -1) {
//     res.end();
//   } else {
//     // Send HTML error page
//     res.render('error', {
//       status: status,
//       message: msg,
//       error: (app.get('env') === 'development') ? err.stack : ''
//     });
//   }
// });

My Quetsion now is, what must be done to get the error handling also working (marked with “not working with ApolloServer” → “General error handler”, “Propagate “404 - not found” error” and the “app.on(‘error’, onError)”)?

I can call the Api Routes, I also can serve to the static route, but if I uncomment the different error handling, the standard “/graphql” route is returning an error like:

Error: Not Found
    at fnErr404 (C:\Users\xxx\__Development\graphql\server.js:29:15)
    at Layer.handle [as handle_request] (C:\Users\xxx\__Development\graphql\node_modules\express\lib\router\layer.js:95:5)
    at trim_prefix (C:\Users\xxx\__Development\graphql\node_modules\express\lib\router\index.js:323:13)
    at C:\Users\xxx\__Development\graphql\node_modules\express\lib\router\index.js:284:7
    at Function.process_params (C:\Users\xxx\__Development\graphql\node_modules\express\lib\router\index.js:341:12)
    at next (C:\Users\xxx\__Development\graphql\node_modules\express\lib\router\index.js:275:10)
    at SendStream.error (C:\Users\xxx\__Development\graphql\node_modules\serve-static\index.js:121:7)
    at SendStream.emit (node:events:520:28)
    at SendStream.error (C:\Users\xxx\__Development\graphql\node_modules\send\index.js:270:17)
    at SendStream.onStatError (C:\Users\xxx\__Development\graphql\node_modules\send\index.js:421:12)

I also serached the internet, but a standard generated express app (with the CLI tool) and a implementation of the ApolloServer doesn’t seems to exist.

Hopefully my description of my question is not to confusing, and someone has a working tip or an example for me.

Thanks
Sascha