Chapter 4: Apollo Server

Strongly Typed Next.js

link Apollo Server

By the end of this lesson, developers will be able to:

  • Create a GraphQL API server with Apollo Server Express
  • Create a MongoDB database connection with Connect Mongo
  • Create environment variables for your localhost server

Introduction

In the previous section, we wrote a strongly typed schema for our GraphQL API server. In this section we will complete the implementation with Apollo Server and Express. Apollo Server Express allows us to build a GraphQL query interface on top of an existing Express server.

Let's begin with an overview of these libraries and their dependencies:

  • Apollo Server: An open-source GraphQL server that is compatible with any kind of GraphQL client. We won’t be using Express for our server in this project. Instead, we will use the power of Apollo Server to expose our GraphQL API.
  • Express: Express is a minimal and flexible Node.js web application framework that provides a robust set of features for web and mobile applications.
  • dotenv: We will use dotenv to load environment variables from our .env file.
  • nodemon: A tool that helps develop Node-based applications by automatically restarting the node application when changes in the directory are detected. We don’t want to be closing and starting the server every time there’s a change in our code. Nodemon inspects changes every time in our app and automatically restarts the server.

link Installation

Heads Up! This lesson's code edits will only affect your API project.

First, change directory into your root level api folder:

cd api

Let's begin with the installation of Apollo Server Express:

npm install apollo-server-express cors ts-node
npm install --save-dev nodemon

Server Scripts

Next, we will edit package.json with two new scripts:

/* ... */
"scripts": {
  "start": "ts-node --transpile-only server/index.ts",
  "dev": "nodemon server/index.ts"
},
/* ... */

The ts-node --transpile-only command will skip type checking and compile the server code.

The nodemon command will run a development server, and reload when any changes occur.

Based on the above scripts, we will also create directories with the following contents:

mkdir session
mkdir server

Each of these directories contains a unique purpose:

  1. api/session: Mongoose creates the server sessions
  2. api/server: Apollo Server Express runs the server

Before moving on to the session and server, we will define a new schema.

link Schema

Create a new file, api/schema/index.ts and insert the following:

import { GraphQLSchema } from 'graphql';
import { buildSchema } from 'type-graphql';
import { ObjectId } from 'mongodb';
import path from 'path';

import { UserResolver } from '../resolvers/UserResolver';
import { AuthResolver } from '../resolvers/AuthResolver';
import { StreamResolver } from '../resolvers/StreamResolver';
import { ObjectIdScalar } from './object-id.scalar';
import { TypegooseMiddleware } from '../middleware/typegoose';

// build TypeGraphQL executable schema
export default async function createSchema(): Promise<GraphQLSchema> {
  const schema = await buildSchema({
    // 1. add all typescript resolvers
    resolvers: [UserResolver, AuthResolver, StreamResolver],
    emitSchemaFile: path.resolve(__dirname, 'schema.gql'),
    // 2. use document converting middleware
    globalMiddlewares: [TypegooseMiddleware],
    // 3. use ObjectId scalar mapping
    scalarsMap: [{ type: ObjectId, scalar: ObjectIdScalar }],
    validate: false,
  });
  return schema;
}

In the above code, we do the following:

  1. Add all typescript resolvers into the schema from the ../resolvers directory.
  2. Apply typegoose middleware to allow GraphQL work with MongoDB Documents.
  3. Apply the scalars map to parse ObjectId properties into string values.

Up next, we will create a free MongoDB Atlas account and cluster.

link MongoDB Atlas

We recommend creating a free tier database cluster at MongoDB Atlas. Sign up for MongoDB Atlas and follow this simple tutorial to create a free cluster.

MongoDB Atlas is the global cloud database service for modern applications. Deploy fully managed MongoDB across AWS, Azure, or GCP. Best-in-class automation and proven practices guarantee availability, scalability, and compliance with the most demanding data security and privacy standards. Use MongoDB’s robust ecosystem of drivers, integrations, and tools to build faster and spend less time managing your database.

We are going to be using an instance of the MongoDB Atlas database.

  1. Navigate to the MongoDB Atlas page
  2. Click "Start Free" and sign up for the MongoDB account
  3. On the "Projects" page click on "New Project" give it a name and create
  4. Add Members. You’re already a member -> hit continue
  5. Build Cluster -> Select Free Tier
  6. Select Cloud Provider & Region and Create Cluster

Builder Book

  1. After the cluster was initialized click on "connect"

Builder Book

  1. Choose a connection method -> Select Connect Your Application and select Node.js

  2. Copy and save your connection string, which should look like the following:

mongodb+srv://<dbname>:<password>@cluster0-yvwjx.mongodb.net/<dbname>?retryWrites=true&w=majority

All set! Up next, we will store the connection string as a new environment variable called MONGO_URL.

link Environment Setup

Before creating the session, we will install dotenv to initialize the MONGO_URL environment value.

npm install dotenv
npm install --save-dev @types/dotenv

Create a new file, api/server/env.ts and insert the following:

import { config } from 'dotenv';
const result = config();

// Only override process.env if .env file is present and valid
if (!result.error) {
  const parsed = result.parsed;
  if (parsed) {
    Object.keys(parsed).forEach((key) => {
      const value = parsed[key];
      if (value) {
        process.env[key] = value;
        console.log(`${key}=${value}`);
      }
    });
  }
}

Using the dotenv library, we are able to determine if a valid .env file is present and log all it's respective values.

Environment File

Create a new file, api/.env and insert the following:

MONGO_URL=REPLACE_WITH_MONGO_URL
URL_APP=http://localhost:3000

Be sure to include your connection url (MONGO_URL) from MongoDB Atlas.

Remember, this file is not meant to be committed with your git repository, as it contains sensitive information.

Up next, we will implement the mongo session.

link Session

Create a new file, api/session/index.ts and insert the following:

import { connect } from 'mongoose';

export default async function createSession() {
  const MONGO_URL = process.env.MONGO_URL || '';
  if (!MONGO_URL) {
    throw new Error('Missing MONGO_URL');
  }
  const options = {
    useNewUrlParser: true,
    useCreateIndex: true,
    useFindAndModify: false,
    useUnifiedTopology: true,
  };
  await connect(MONGO_URL, options);
}

In the above code, we initialize a MongoDB connection. If any errors are thrown, they will be handled by the server at runtime.

link Server

Create a new file, api/server/index.ts and insert the following:

import './env';
import 'reflect-metadata';
import { ApolloServer } from 'apollo-server-express';
import express from 'express';
import cors from 'cors';

import createSchema from '../schema';
import createSession from '../session';

const port = process.env.PORT || 8000;

async function createServer() {
  try {
    // 1. create mongoose connection
    await createSession();
    // 2. create express server
    const app = express();

    // allow CORS from client app
    const corsOptions = {
      origin: 'http://localhost:3000',
      credentials: true,
    };
    app.use(cors(corsOptions));

    // allow JSON requests
    app.use(express.json());

    const schema = await createSchema();

    // 3. create GraphQL server
    const apolloServer = new ApolloServer({
      schema,
      context: ({ req, res }) => ({ req, res }),
      introspection: true,
      // enable GraphQL Playground with credentials
      playground: {
        settings: {
          'request.credentials': 'include',
        },
      },
    });

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

    // start the server
    app.listen({ port }, () => {
      console.log(
        `🚀 Server ready at http://localhost:${port}${apolloServer.graphqlPath}`
      );
    });
  } catch (err) {
    console.log(err);
  }
}

createServer();

The ApolloServer is initialized with an Express server, MongoDB session middleware and GraphQL schema. Most of the logic is abstracted out to simplify this file, so here's an overview:

  1. We initialize a MongoDB database connection.
  2. We initialize an Express server and apply cors, json and session middleware.
  3. We initialize an Apollo GraphQL server with the schema definition, context and playground options.

Note: Setting 'request.credentials': 'include', in the playground options is necessary for testing authenticated requests.

link Testing

It's time to fire up the server and test our queries.

npm run dev

Be sure to visit the playground and test your queries at http://localhost:8000/graphql

Congratulations!

Today we completed the GraphQL API server. Up next, we will begin to integrate an Apollo Client with the frontend.

link References

format_list_bulleted
help_outline