Frontend Serverless with React and GraphQL

Start at the beginning and work your way through this project. The code for each step as well as the finished project can be found in the Github repository.


40. Add GraphQL Proxy

Objective: We want to add an api route to our next.js server that will handle requests coming from the user and forward them onto the GraphCMS server.

In this step, we want to create an api route on our Next.js server that will accept client graphQL requests. This will allow the api service itself to store all the GraphCMS authentication keys and we will add additional gatekeeping functionality to make sure that users aren't sending malicious requests.

First we want to install the proxy server package that will allow us to forward on requests from the user to GraphCMS:

bash

npm install --save graphql-request

Next, we should create the graphql proxy service. This will be a new api endpoint called graphql. In the function, we will use the graphQLClient.rawRequest feature to forward on variables and queries that we get from a user. If an error occurs, we will return a formatted array of errors back to the user.

pages/api/graphql.ts

import getConfig from 'next/config';
import { GraphQLClient } from 'graphql-request';
const { serverRuntimeConfig } = getConfig();
const { BRANCH, GRAPHCMSURL, GRAPHCMSPROJECTID } = serverRuntimeConfig.graphcms;
const graphqlEndpoint = `${GRAPHCMSURL}/${GRAPHCMSPROJECTID}/${BRANCH}`;
export const graphQLClient = new GraphQLClient(graphqlEndpoint, {});
async function proxyGraphql(req, res) {
try {
const { variables, query } = req.body;
const data = await graphQLClient.rawRequest(query, variables);
res.json(data);
} catch (e) {
res.json({ data: {}, errors: [{ message: e.message }] });
}
}
export default proxyGraphql;

We will need to add a BACKEND_URL variable which will tell the user to connect to this new /graphql endpoint. Make sure to add the BACKEND_URL to your .env file, and it should look like: BACKEND_URL=http://localhost:8000/api/graphql. Now we can add this variable to the next.config.js file like so:

next.config.js

require('dotenv').config();
const {
BRANCH,
GRAPHCMSURL,
GRAPHCMSPROJECTID,
domain,
clientId,
clientSecret,
scope,
redirectUri,
postLogoutRedirectUri,
cookieSecret,
BACKEND_URL,
} = process.env;
module.exports = {
publicRuntimeConfig: {
backend: { BACKEND_URL },
},
serverRuntimeConfig: {
graphcms: {
BRANCH,
GRAPHCMSURL,
GRAPHCMSPROJECTID,
},
auth: {
domain,
clientId,
clientSecret,
scope,
redirectUri,
postLogoutRedirectUri,
},
cookieSecret,
},
};

Make sure that GRAPHCMSID, GRAPHCMSPROJECTID, and GRAPHCMSURL have been moved into the serverRuntimeConfig block since we will be protecting those variables by only alllowing the api itself to access them.

Now we can turn to the apolloClient.ts file. We need to update the env block so that we are importing the BACKEND_URL instead of BRANCH, GRAPHCMSPROJECTID and GRAPHCMSURL:

components/WithApolloClient.tsx

import { ApolloClient } from 'apollo-client';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { HttpLink } from 'apollo-link-http';
import fetch from 'isomorphic-unfetch';
import getConfig from 'next/config';
const { publicRuntimeConfig } = getConfig();
const { BACKEND_URL } = publicRuntimeConfig.backend;
export default function createApolloClient(initialState, ctx) {
// The `ctx` (NextPageContext) will only be present on the server.
// use it to extract auth headers (ctx.req) or similar.
return new ApolloClient({
ssrMode: Boolean(ctx),
link: new HttpLink({
uri: BACKEND_URL, // Server URL (must be absolute)
credentials: 'same-origin', // Additional fetch() options like `credentials` or `headers`
fetch,
}),
cache: new InMemoryCache().restore(initialState),
});
}

We also change the uri from the ApolloClient block to use the BACKEND_URL variable.

If we now try and use our app, we will run into some issues getting some of the queries to load. This is because of a change made in GraphCMS v2.0 where it will error out if we name our queries and mutations but don't pass that same name as an operationName. Let me show you by example:

graphql/mutations/createRecipe.ts

// more code above
mutation createUserLikeGraphQL($data: UserLikeCreateInput!) {
createUserLike(data: $data) {
id
user
recipe {
id
}
}
}
`;

Here when we send this mutation off to the backend, Apollo is smart enough to add a parameter to the request called operationName with the field createUserLikeGraphQL. The graphql-request library unfortunately does not support adding an operationName. Since graphql-request is now the library sending the request to GraphCMS, the backend throws an error when it sees a named query in the request but does not see that same name as an operationName parameter.

Luckily, the fix is easy. Go through each query and mutation file in the graphql folder and remove the name of the query or mutation. This will be the text right after query or mutation on line 4 of each of the 10 files.

So this:

graphql/mutations/createRecipe.ts

// more code above
mutation createUserLikeGraphQL($data: UserLikeCreateInput!) {
createUserLike(data: $data) {
id
user
recipe {
id
}
}
}
`;

becomes this:

graphql/mutations/createRecipe.ts

// more code above
mutation ($data: UserLikeCreateInput!) {
createUserLike(data: $data) {
id
user
recipe {
id
}
}
}
`;

And that's it- save the files and you should be in business.