loading...

CORS in Apollo Client & Apollo Server

doylecodes profile image Ryan Doyle ・3 min read

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}`)
);

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.

Posted on May 1 '19 by:

Discussion

markdown guide
 

Why not just add corsOptions on server.applyMiddleware

const corsOptions = {
  origin: 'http://localhost:3000',
  credentials: true
}

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

server.applyMiddleware({ app, cors: corsOptions });
 

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?

 

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.

 

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

 

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 } })

 

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!

 

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