DEV Community

Ryan Doyle
Ryan Doyle

Posted on

CORS in Apollo Client & Apollo Server

This is a follow-up

Ok, a while back I asked a question and spent a few days trying to figure this out. I'm hoping that this can direct someone trying to solve the problem in the right direction. As a reference, this is the original post.

My problem

So, my problem was that I had set up an Apollo Server with the backend hosted from a /backend directory and the frontend hosted from a separate directory at /frontend. My frontend was running on localhost:3000 and the backend I had running at localhost:4000. I was running into various problems with CORS as I tried to pass cookies along with requests, and I couldn't figure out what was going on. I also had a difficult time figuring out how to pass certain things (like user data) with requests.

My problem was, I was trying to pass along specific cors options, and I didn't know that Apollo Server came with default cors settings. Those settings were overriding and conflicting with what I needed

The Solution

My solution ended up being 2 parts:

  1. Using the package apollo-server-express and the cors middleware.
  2. Disabling the Apollo Server cors to avoid conflicts.
const express = require('express');
const { Prisma } = require('prisma-binding');
const { ApolloServer } = require('apollo-server-express');
const { importSchema } = require('graphql-import');
const cookieParser = require('cookie-parser');
const jwt = require('jsonwebtoken');
const cors = require('cors');
require('dotenv').config();

const typeDefs = importSchema('./src/schema.graphql');
const Query = require('./src/resolvers/Query');
const Mutation = require('./src/resolvers/Mutation');

const db = new Prisma({
  typeDefs: './generated/prisma.graphql',
  endpoint: process.env.DB_ENDPOINT,
  secret: process.env.DB_SECRET
});

const server = new ApolloServer({
  typeDefs,
  resolvers: {
    Mutation,
    Query
  },
  context: req => ({ ...req, db }),
});

const app = express();

var corsOptions = {
  origin: process.env.FRONTEND_URL,
  credentials: true // <-- REQUIRED backend setting
};

app.use(cors(corsOptions));
app.use(cookieParser());
app.use((req, res, next) => { // checks for user in cookies and adds userId to the requests
  const { token } = req.cookies;
  if (token) {
    const { userId } = jwt.verify(token, process.env.USER_SECRET);
    req.userId = userId;
  }
  next();
})
app.use(async (req, res, next) => {
  if (!req.userId) return next();
  const user = await db.query.user(
    { where: { id: req.userId } },
    '{id, permissions, email, name}' //the graphql query to pass to the user query
  );
  next();
})

server.applyMiddleware({
  app,
  path: '/',
  cors: false, // disables the apollo-server-express cors to allow the cors middleware use
})

app.listen({ port: 4000 }, () =>
  console.log(`🚀 Server ready at http://localhost:4000${server.graphqlPath}`)
);
Enter fullscreen mode Exit fullscreen mode

This is my server/app set up as of now with everything working perfectly! Here are the main points and why I ended up implementing it that way.

  • Using apollo-server-express instead of apollo-server: This essentially allows you to create an express app and pass it to the server instead of using the vanilla app that Apollo Server creates on its own. It also allows you to create middlewares just as you would with a generic express based application and server!
  • app.use(cors(corsOptions)): For more granular cors options (I wanted the server to only listen to localhost:3000) you use the cors middleware and the options to listen to the origin you want, as well as the option to pass on credentials with requests.
  • server.applyMiddleware: It is here that you pass to the Apollo Server the express application, as well as the other important part, turn off the default cors options. Even though you turn off the default options, you are using the cors middleware in the express app you pass to the server, so it's all handled there.

Top comments (12)

Collapse
 
radhasatam profile image
Radha Satam

Why not just add corsOptions on server.applyMiddleware

const corsOptions = {
  origin: 'http://localhost:3000',
  credentials: true
}
Enter fullscreen mode Exit fullscreen mode

This worked for me and I didn't have to use the cors dependency

server.applyMiddleware({ app, cors: corsOptions });
Enter fullscreen mode Exit fullscreen mode
Collapse
 
theknary profile image
knary

This is the only method I got cors to work with. Thanks!

Collapse
 
mwangikibui profile image
mwangiKibui

Worked out too. Thanks.

Collapse
 
phavor profile image
Favour George

Hi Ryan,

I am using the apollo-server to setup a node apollo server. I just deployed to heroku a production verion of my app, but I can't seem to communicate with it via the React apollo-client code. It complains of cors. I have had no success in fixing it. I have tried a lot of solutions online, but no success yet.

require('dotenv').config()
const { MONGODB_URI, PORT, SSL_PORT, MONGODB_URI_LOCAL, NODE_ENV } = process.env
const { ApolloServer } = require('apollo-server')
const fs = require('fs')
const path = require('path')
const https = require('https')
const DB = require('./database')
const superAdminDetails = require('./config/superAdmin.config')
const formatError = require('./utils/formatError')
const allowedOrigins = ['https://staylow.herokuapp.com', 'http://staylow.herokuapp.com', 'staylow.herokuapp.com', 'http://localhost:3000']

const typeDefs = require('./graphql/types')
const resolvers = require('./graphql/resolvers')
const dataSources = require('./graphql/datasources')

const { getUser } = require('./utils/auth')

const DB_URL = NODE_ENV ? MONGODB_URI : MONGODB_URI_LOCAL
new DB(superAdminDetails).connect(DB_URL)

const server = new ApolloServer({
  cors: {
    origin: '*' // I have tried `allowedOrigins` here but no sucess
  },
  typeDefs,
  resolvers,
  formatError,
  context: async ({ req }) => {
    const token = req.headers.authorization || '';

    const AuthUser = await getUser(token);

    return { AuthUser };
  },
  dataSources: () => (dataSources),
})

server.listen(PORT).then(({ url }) => {
  console.log(`🚀 Server ready at ${NODE_ENV ? 'https://oneidserver.herokuapp.com' : url}`)
})

https.createServer({
  key: fs.readFileSync(path.join(process.cwd(), '/key.pem')),
  cert: fs.readFileSync(path.join(process.cwd(), '/cert.pem'))
})
  .listen(SSL_PORT || 4141, () => {
    console.log(`HTTPS server running on ${
      NODE_ENV ?
        'https://oneidserver.herokuapp.com:' : 'https://localhost:'}${SSL_PORT || 4141}/`)
  }).setTimeout(780000)

Do you why this is happening?

Collapse
 
doylecodes profile image
Ryan Doyle

Not sure off hand, it's difficult to tell without any errors I can get a look at. One thing is, I didn't use CORS in Apollo client, which you still are. I added apollo-server-express and used that.

Collapse
 
phavor profile image
Favour George

I have switched to apollo-server-express and it still had same issues. I am not using cors from my client app

Collapse
 
tmns profile image
tmns

Just spent several hours trying to figure out what on Earth I could possibly not be doing correct. Somehow, among all the tutorials / q&a's / blog posts / etc out there, it seems none of them mention the crucial point that you have to turn off the default cors option. You saved my day bud! Thanks!

Collapse
 
autsada profile image
autsada

For me I just have to set fetchOptions in the frontend code to {credentials: 'included'}, I use Urql instead of Apollo-Client though.

Configuration for Apollo-Server-Express is as normal (didn't have to use the cors package).

apolloServer.applyMiddleware({ app, cors: { origin: FRONTEND_URL, credentials: true } })

Collapse
 
wiseintrovert_31 profile image
Wise Introvert

Ryan Doyle, great article. I'm facing the same issue with my setup. Would be awesome if you could take a look at it: stackoverflow.com/questions/642075...

Collapse
 
pumuckelo profile image
pumuckelo

omg you're the best, this apollo server cors: false nearly killed me.

Collapse
 
julianporras8 profile image
Julian Porras

Thanks a lot!
You Saved my day

Collapse
 
matrayu profile image
Matrayu

Lifesaver. Thanks so much for this write up.