Enabling CORS with NestJS and GraphQL on localhost

Published 2020-05-26

Mountain view with some yellow trees sprinkled.

Photo by Shane Rounce on Unsplash

I recently had a somewhat frustrating experience with enabling CORS in a NestJS app that uses GraphQL Apollo server and cookie authentication. The client and server apps are both running on localhost but on two different ports. A few StackOverflow questions and Github issues exist but none had answers that worked for me.

The Red Herring 🐟

Many resources online will say to enable CORS and put configuration on app, like so:

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.enableCors({  // wrong!  in my case, anyway
    origin: 'http://localhost:3000',
    allowedHeaders: 'Content-Type, Accept',
    credentials: true,
  await app.listen(3001);
  console.log(`Application is running on: ${await app.getUrl()}`);


But be careful!! This may work for building a REST API, but not for GraphQL!

Even though origin is set to the correct url, Chrome will still show this as repsonse headers:

Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: *

But Firefox appears to show the missing piece. The browser appears to send an OPTIONS preflight request to /graphql that does have the correct origin set, but the subsequent POST /graphql does not have origin set.

OPTIONS /graphql
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: Content-Type, Accept
Access-Control-Allow-Methods: GET,HEAD,PUT,PATCH,POST,DELETE
Access-Control-Allow-Origin: http://localhost:3000
POST /graphql
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: *

The Solution 🎉

Since I'm using GraphQL, what worked was to actually put the CORS configuration in the GraphQLModule#forRoot() options.

  imports: [
      cors: {
        origin: 'http://localhost:3000',
        credentials: true,

The first OPTIONS request to /graphql looks the same, but the subsequent POST has the correct headers now:

Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: http://localhost:3000

Production settings may require this to change but for now this is working on a localhost setup.

Happy coding 👩‍💻