Fullstack Apollo+React GraphQL setup

Mindaugas Nakrosis
4 min readMay 14, 2022

--

In my recent commercial greenfield 🍏 project we decided to use GraphQL. The main reasons why our decision shifted towards it:

  • We were going to have a mobile and web applications which consume the same API in slightly different ways
  • We needed some built-in frontend caching
  • The data that we were going to work with was pretty complex and hard for the client to consume
  • Automatic TypeScript types generation for the frontend was a requirement

So we decided to give Apollo a try as it is the most widely used GraphQL client. It has its pros and cons and might not suit every project’s needs but I will show you the setup we have.

Initial setup

The configuration needed to spin everything up is quite well documented and easy to get into place.

React

import { ApolloProvider } from '@apollo/client/react';
import { ApolloClient, ApolloLink, createHttpLink, InMemoryCache } from '@apollo/client';
const cache = new InMemoryCache();

const httpLink = createHttpLink({
uri: http://localhost:4000,
});
const client = new ApolloClient({
link: ApolloLink.from([httpLink]),
cache,
});
const App = () => {
return (
<ApolloProvider client={client}>
<Routes />
</ApolloProvider>
)
}

Steps:
1. An Apollo client with inMemoryCache (which caches stuff, cool right?)
2. Assign url to the server which I will soon show how to setup
3. That’s already it. Keep on reading!

And you are good to go. 🎆 There is so much more that we have implemented since:

  • Authentication
  • Error handling
  • Cache configuration
  • Local variables

But I won’t get into all of that in this article.

GraphQL server

Our GraphQL server basically consists of:

  • Datasources (REST)
  • Schema
  • Resolvers

This is the full server configuration:

import { ApolloServer } from "apollo-server";
import {
ApolloServerPluginLandingPageDisabled,
ApolloServerPluginLandingPageGraphQLPlayground,
} from "apollo-server-core";
import depthLimit from "graphql-depth-limit";
import typeDefs from "./schemas";
import resolvers from "./resolvers";
import dataSources from "./datasources";
console.log(`🔥 process.env.NODE_ENV: ${process.env.NODE_ENV}`);
if (process.env.NODE_ENV === "development") {
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
}
const isProduction = () => {
return process.env.NODE_ENV === "production";
};
const server = new ApolloServer({
debug: true,
typeDefs,
resolvers,
dataSources,
context: async ({ req }) => {
return { req };
},
validationRules: [depthLimit(5)],
introspection: isProduction() ? false : true,
formatError: (err) => {
err.message = err?.extensions?.response?.body?.title || err.message;
return err;
},
plugins: [
isProduction()
? ApolloServerPluginLandingPageDisabled()
: ApolloServerPluginLandingPageGraphQLPlayground(),
],
});
server.listen({ port: process.env.PORT || 4000 }).then((listenResponse) => {
console.log(`🚀 Server is running at ${listenResponse.url}`);
});

You can analyse what each line means yourself as it is pretty self-explanatory.

Datasources

These as you can expect input our GraphQL with data. You can use whatever dataSource type you want. Not only REST. I will show you how it is done with REST though as it is by far the most popular usage case.

This is datasources/index.ts:

import { DataSources } from "apollo-server-core/dist/graphqlOptions";
import TodoApi from "./TodoApi";
export interface IDataSources {
todoApi: ITodoAPI;
}
const dataSources = (): DataSources<IDataSources> => {
return {
todoApi new TodoApi(),
};
};
export default dataSources;

Then you need to define your TodoApi:

import { ITodoApi } from "../types/todoTypes";
import BaseRestDataSource from "./BaseRestDataSource";
class SearchApi extends BaseRestDataSource implements ITodoApi {
constructor() {
super();
this.baseURL = // Your REST service url here
}
todos() {
return this.get("Todo");
}
}
export default TodoApi;

Then define ITodoApi interface:

export interface ITodoApi {
todos(): Promise<Todo>;
}

interface Todo {
id: number;
name: string;
}

And that is your dataSource ready! ✅

Schema (type definitions)

This is your index file:

import { gql } from "apollo-server";
import todoDefs from "./todoDefs";
const baseTypeDefs = gql`
type Query
type Mutation
`;
const schemas = [
todoDefs,
];
export default schemas;

Then define todo schema:

import { gql } from "apollo-server";const todoTypeDefs = gql`
extend type Query {
todos: Todo
}
type Todo {
id: Int!
name: String!
}
`;
export default todoTypeDefs;

Exclamation marks ️️️❗means that it is non-nullable.
And your schema is good to go! ✅

Resolvers

This is the most interesting part as resolvers enabled us to shape the input that we receive into whatever we want. Resolvers is one of the things that makes GraphQL so powerful 👊. Here is our index file:

import GraphQLJSON from "graphql-type-json";
import merge from "lodash/merge";
import todoResolvers from "./todoResolvers";
export default merge(
{},
todoResolvers,
);

And the todoResolver:

import { IDataSources } from "../types";interface IAppContext {
dataSources: IDataSources;
}
const queryResolvers = {
todos: (
// unused in this case
parent: unknown,
// no parameter provided to this query
parameters: unknown,
// the REST datasources we can use to make API calls
{ dataSources }: IAppContext
) => {
return dataSources.todoApi.todos();
},
};
export default {
Query: queryResolvers,
};

The resolver accepts three parameters:

  1. Parent 👨 is available if you are trying to access child 👶 of a parent (nested data). e.g:
todos {
properties {
propertyName
propertyValue
}
}

In this case if you were accessing properties in Property resolver the parent would be Todo.

2. Parameters. If you provide parameters to the query or mutation they will be accessible in the resolvers.

3. Datasource. It is the REST datasource that we defined and can be used to fetch data in the resolvers.

That is an abstract setup of Apollo GraphQL server 🔥. There is so much more to it that I left out intentionally or not as I don’t want this article to become a huge read 📚.

If you are still reading, thanks for your attention!

Photo by Kelly Sikkema on Unsplash

--

--

No responses yet