How to build an Apollo GraphQL server with TypeScript and Webpack Hot Module Replacement

Derek Fong
Jan 2 · 10 min read
Apollo Saturn V (Image Source: WallHere)

UPDATE 14 May 2019: Updated sample codes to fix breaking changes caused by `clean-webpack-plugin`. Also added a CodeSandbox live demo at the end of this post.

Let’s build an Apollo GraphQL Server with TypeScript and Webpack HMR!

Prerequisites 🕺

  • Preferably basic understanding of the fundamental GraphQL principles.
  • Preferably general knowledge with TypeScript. That being said, general JavaScript knowledge should be sufficient to understand the topics covered in this post. I will try my best to explain when it comes to TypeScript-specific concepts.

Ready to Build Something? 🔨

First Things First ☝🏻

$ mkdir apollo-server-demo && cd apollo-server-demo
$ npm init --yes

Install TypeScript 📝

$ npm install --save-dev typescript

💡 Note: package-lock.json is automatically generated when you install any NPM package for the first time. This is a feature introduced since NPM version 5 and you SHOULD commit package-lock.json along with package.json if you are using source control like Git. James Quigley has written a post explaining what package-lock.json is, and why it is needed.

2. Initialize TypeScript configuration with a few key options:

$ npx tsc --init --rootDir src --outDir dist --lib dom,es6 --module commonjs --removeComments

💡 Note: npx is a tool (bundled with NPM version 5.2 or above) for running NPM packages that are installed in local node_modules folder. This post covers npx usage in detail.

This will create a tsconfig.json file in your project’s root directory. Add include and exclude options to tsconfig.json. Your tsconfig.json should look like this (comments omitted).

📄 File: tsconfig.json

3️. Verify TypeScript is setup correctly:

Create new a folder named src and create a new file main.ts in the folder:

📄 File: src/main.ts

Now try to compile TypeScript codes:

$ npx tsc

Once compiled, you will notice a new dist folder is created. TypeScript Compiler knows where to find the input TypeScript files and where to output the compiled file(s) because we have specified the options in tsconfig.json from Step 2.

Now try to run the compiled JavaScript file:

$ node dist/main

And you should see the output:

> Hello World!

Now, let’s say we want to modify main.ts to display a message other than Hello World:

📄 File: src/main.ts

In order to see the changes in action, we will need to re-compile the TypeScript codes, and re-run the compiled JavaScript file:

$ npx tsc && node dist/main

> foo bar

As you can see, compile-and-run soon became a tedious task, especially during the development phase. We need to find a way to automate this process.

Introducing Webpack (with HMR) 📦

Webpack is a JavaScript module bundler. It is also capable of transforming, bundling, or packaging just about any resource or asset.

We are going to use Webpack to transform and bundle codes written in TypeScript to JavaScript. Also, we are going to create Webpack configuration files targeting specific environments (namely development and production). Moreover, we are going to enable Webpack’s Hot Module Replacement (HMR) to speed up development.

1️. Install Webapck NPM packages and plugins:

$ npm install --save-dev @types/webpack-env clean-webpack-plugin ts-loader webpack webpack-cli webpack-merge webpack-node-externals

📦 @types/webpack-env: TypeScript type definitions for Webpack.

📦 clean-webpack-plugin: We use this plugin to clean up our outputdist folder every time before Webpack builds our code.

📦 ts-loader: TypeScript loader for webpack.

📦 webpack and webpack-cli: Webpack essentials.

📦 webpack-merge: We use this plugin to manage our environment-specific configuration files (development, production, etc. ).

📦 webpack-node-externals: Exclude Node modules that should not be bundled.

Up to this point, package.json should look similar to this:

📄 File: package.json

2️. Add Webpack configuration files to the project’s root directory:

We are going to create 3 Webpack configuration files:

🔧 webpack.development.js: Development-specific Webpack configurations. For example, enable watch option, Hot Module Replacement (HMR), etc.

🔧 webpack.production.js: Production-specific Webpack configurations. For example, enable Webpack’s production mode.

🔧 webpack.common.js: Webpack configurations which apply to all environments. Both webpack.development.js and webpack.production.js “inherits” common configurations using the webpack-merge plugin.

💡 Note: You may refer to this guide from Webpack for more detailed setup.

📄 File: webpack.common.js

📄 File: webpack.development.js

📄 File: webpack.production.js

3️. Enable Webpack Hot Module Replacement in the source code:

📄 File: src/main.ts

4️. Add NPM build and start scripts:

In Step 2, we have decided to name Webpack configuration files as webpack.development.ts and webpack.production.js for a reason: development and prodcution are actually referring to the value of NODE_ENV environment variable. By utilizing the value specified in NODE_ENV, we can easily switch between different environment configurations with NPM scripts defined in package.json. This approach is especially useful if you are setting up deployment strategies later on with Docker, Heroku, etc

(TODO: I am going to write a separate post to cover various application deployment strategies in detail. )

📄 File: package.json

For example, if we want to build with Webpack’s production configurations, we could simply run:

$ NODE_ENV=production npm run build

And you will see a compiled server.js file in the dist folder.

5️. Verify Webpack HMR is Setup Correctly:

Run NPM build with development configurations:

$ NODE_ENV=development npm run build

which should display an output similar to:

webpack is watching the files…Hash: 23976326d5ac19cc44e4
Version: webpack 4.28.3
Time: 1191ms
Built at: 01/01/2019 10:23:34 PM
Asset Size Chunks Chunk Names
main.js 76.3 KiB main [emitted] main
Entrypoint main = main.js
[0] multi webpack/hot/poll?1000 ./src/main.ts 40 bytes {main} [built]
[./node_modules/webpack/hot/log-apply-result.js] (webpack)/hot/log-apply-result.js 1.27 KiB {main} [built]
[./node_modules/webpack/hot/log.js] (webpack)/hot/log.js 1.11 KiB {main} [built]
[./node_modules/webpack/hot/poll.js?1000] (webpack)/hot/poll.js?1000 1.15 KiB {main} [built]
[./src/main.ts] 172 bytes {main} [built]

Now, open a new Terminal and run the compiled code:

$ npm start

Which should display output:

> Hello World!

To test Webpack’s HMR, try to modify src/main.ts to output a message other than "Hello World".

📄 File: main.ts

Once you’ve saved the changes, you will see the following output:

Module disposed. 
foo bar
[HMR] Updated modules:
[HMR] - ./src/main.ts
[HMR] Update applied.

Now we confirmed Webpack HMR is working with TypeScript! You may press ctrl+ c on the keyboard to stop the build and run processes on both Terminals.

Setup Apollo Server 🚀

🤔 What is Apollo Server?

Apollo Server is actually part of the Apollo GraphQL Platform. It is an open-sourced library from the Meteor Development Group (MeteorJS, anyone?), which provides a suite of tools to create GraphQL server embracing best practices for the industry.

The Apollo GraphQL Platform

In its simplest form, a GraphQL server is made up of three core components:

  • GraphQL server library, such as Apollo Server
  • Schemas: What types of data are available
  • Resolvers: How to fetch the data required

To demonstrate how easy it is to set up an Apollo Server, we are going to create a minimal server which contains only one Query.

1️. Install Apollo Server and dependencies:

$ npm install --save apollo-server graphql

📦 apollo-server: The Apollo Server library.

📦 graphql: The library used to build and execute GraphQL schemas.

2️. Create type-defs.ts

📄 File: src/type-defs.ts

This file uses the Schema Definition Language (DSL) to define what type(s) of data is available from the server. In this case, we have defined a testMessage query is available, which returns a string.

You may also notice the triple-double quotes “”” in the code. These are markdown-enabled comments within the schema supported by GraphQL. It helps data consumers to discover and understand the types of data provided by the server from tools like GraphQL Playground.

3️. Create resolvers.ts

📄 File: src/resolvers.ts

Resolvers are simply pure functions defining how data are fetched when requested.

In general, every resolver in a GraphQL schema accepts four positional arguments:

fieldName(obj, args, context, info) { result }

That being said, in our example, since our testMessage query always return a constant Hello World! string, we do not need to worry about the arguments passing into the resolver function for now.

💡 Note: As your project grows, your Schemas and Resolvers will get more complex and will be difficult to maintain in one single file. Apollo has provided suggestions to modularize your code. Alternatively, the merge-graphql-schemas NPM package is another good option to consider when modularizing your GraphQL code.

UPDATE: Checkout GraphQL Modules, which provides toolset of libraries which helps to build modularized code that scales!

4️. Create Apollo Server in main.ts

📄 File: src/main.ts

We have created our first Apollo Server with the schema and resolver defined in Step 2 and Step 3.

We have fired up our Apollo Server with default options, which will be accessible via http://localhost:4000.

Finally, we have added the code to stop the server running when HMR kicks in, and re-start the server after when HMR completes.

5️. It’s time to test our server! 🕑

Open a new Terminal to build the server in development mode:

$ NODE_ENV=development npm run build

Open another Terminal to start the server:

$ npm start

Open a Web browser and navigate to http://localhost:4000, which should bring you to the Server’s GraphQL Playground.

Now if you try to input query and click the “Play” ▶️ button:

query {

It should output the result:

"data": {
"testMessage": "Hello World!"

You can also read the schema’s documentation by clicking the DOCS tag on the right-hand side of the screen:

Great! We have built our first Apollo Server! 👏🏻

🧐 But there is one problem…

Our Apollo server is now up and running which is great…but our server is running with default options. When building a real-world production app, chances are you will need to provide custom options when setting up your Apollo Server, especially when the Apollo Server API comes with a whole list of configuration options available for tweaking.

For example, you might want to use a port other than 4000 when running the server in production. Or for any reason, you might want to enable GraphQL Playground in production (GraphQL Playground is disabled when NODE_ENV is set to production by default).

We want our server to be flexible enough to handle various environment-specific configurations. How do we achieve this?

Environment Variables come to the rescue 💪🏻

If you have experience in building Javascript applications, you probably already know you can access environment variables via the process.env object in your JavaScript apps:

# Set environment variable `FOO` in Terminal
$ export FOO=bar
# Or in Windows Command Line
$ set FOO=bar
// your-app.jsconsole.log(provess.env.FOO); // OUTPUT: bar

This works fine…until you need to switch between projects with a list of environment variables with different values.

Luckily, there is an NPM package called dotenv which aims to address this issue.

The concept is pretty simple:

  • Define all your environment-specific variables in a single plain text file.
  • Reference the environment variables with the process.env object throughout your code.
  • Run the code with --require dotenv/config option.

1️. Install dotenv as devDependencies:

$ npm install --save-dev dotenv

2️. Add a .env file in the project root:

📄 File: .env

💡Note: You SHOULD NOT commit .env if you are using any source control. For example, add .env to .gitignore if you are using Git.

3️. Create environment.ts

While you could access environment variables directly with process.env, I encourage you to create a file to hold all your environment variables. This will be much easier if you need to make any changes to the variables or refactoring down the track.

📄 File: src/environment.ts

4️. Add Apollo Server options

We’ve added introspection and playground options to be controlled by environment variables. We’ve also asked our server to run on the port specified on the environment’s PORT variable.

📄 File: main.ts

5️. Add start:env NPM script to package.json

📄 File: package.json

6️. Test-Run the Server with Environment Variables

Open a new Terminal to build the server in development mode:

$ NODE_ENV=development npm run build

Open another Terminal to start the server with dotenv:

$ npm run start:env

Your Apollo Server should be accessible via http://localhost:4001.

You can also try to change the APOLLO_PLAYGROUND value in .env to false, re-run your build and start:env NPM scripts and confirm GraphQL Playground is no longer accessible via http://localhost:4001 on the web browser.

Below is a CodeSandbox live demo:

Wrapping up

I believe GraphQL is the next generation of API, and the Apollo GraphQL toolsets sound very promising. Therefore, I think it is worth to invest time and effort to learn this technology.

We are only scratching the surface here for GraphQL and the Apollo Platform. In fact, there are a lot more to be covered. Below are some of the topics that I am thinking of to be covered next. Please leave comments and let me know which ones you’re interested in 😉

🍃 Apollo Server: Connect to MongoDB with Mongoose
🔐 Apollo Server: Authentication and Authorization with JWT
✅ Apollo Server: Test schema and resolvers
🐳 Apollo Server: Deploy dockerized Apollo Server on Heroku
🌚 CircleCI: Test-build-deploy with CircleCI 2.1 Orbs
🏎Apollo Engine: Production-ready
🎨 Apollo Client: Angular + NativeScript or React + ReactNative

Enjoy this post? Please leave some claps 👏🏻
Questions or feedback? Please leave comments below 💁🏻‍♂️

This is no longer updated. Go to instead

Derek Fong

Written by

Web Developer • Angular • React • NativeScript • GraphQL

This is no longer updated. Go to instead

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade