apollo-server-express GraphQL + TypeScript + esm

Apollo released Apollo Server 3 in July 2021 with the goal of offering more flexibility and a leaner code-base to build new features.

The core package for the server is apollo-server. If you want to integrate Apollo into an existing Express application or wish to use more advanced features, you’ll need apollo-server-express.

The documentation is user-friendly but does not provide a turn-key solution to get you started.
Apollo Server Express needs to start asynchronously which caused some problems.

In this article, I’ll show you the steps to create a simple Apollo Server with TypeScript, nodemon, ts-node and ESM (ES Modules).


Create a new project with npm (you’ll need Node.js 12 or later).

I use Linux, so the code in my blog post works for Unix. If you’re a Windows user, you’ll probably need to adjust some of the commands.

mkdir apollo-3-ts && cd apollo-3-ts
npm init -y

Install dependencies:

npm i apollo-server-express apollo-server-core express graphql
npm i --save-dev typescript nodemon ts-node tsc-watch @types/node @types/express

Setup TypeScript:

npx tsc --init

The above command creates a new file called tsconfig.json. Adjust the following parts in the file:

  "module": "ES2020",
  "moduleResolution": "Node",
  "outDir": "./dist"

Compiled files (from TypeScript to JavaScript) will land in the dist folder.

Add these lines to package.json to enable ECMAScript modules and allow imports from your compiled TypeScript files:

  "type": "module",
  "exports": "./dist/index.js"

Now, we will add a script for the development server into package.json:

  "scripts": {
    "watch": "nodemon --watch './**/*.{ts,graphql}' --exec 'node --experimental-specifier-resolution=node --loader ts-node/esm' src/index.ts",
    "dev": "tsc-watch --onSuccess \"npm run watch\""

You can find more details in the links at the end of the article.

Apollo Server Setup

Create a new folder called src with a file index.ts.

mkdir src && touch src/index.ts

You should now have a file structure like this:

apollo-3-ts <- folder name
├── node_modules
├── package.json
├── package-lock.json
├── src
│   └── index.ts
└── tsconfig.json

Copy the next code snippet into src/index.ts. The code is from the documentation with a fix to promisify the http listener with TypeScript.

import { ApolloServer, gql } from 'apollo-server-express'
import { ApolloServerPluginDrainHttpServer } from 'apollo-server-core'
import express from 'express'
import http from 'http'

const typeDefs = gql`
  type Query {
    hello: String

const resolvers = {
  Query: {
    hello() {
      return 'world'

async function listen(port: number) {
  const app = express()
  const httpServer = http.createServer(app)

  const server = new ApolloServer({
    plugins: [ApolloServerPluginDrainHttpServer({ httpServer })],
  await server.start()

  server.applyMiddleware({ app })

  return new Promise((resolve, reject) => {
    httpServer.listen(port).once('listening', resolve).once('error', reject)

async function main() {
  try {
    await listen(4000)
    console.log('🚀 Server is ready at http://localhost:4000/graphql')
  } catch (err) {
    console.error('💀 Error starting the node server', err)

void main()

The code bootstraps the Apollo server with a minimal “Hello, world” resolver to get you started.

Now you can fire off the server from the command line:

npm run dev

You should be able to reach the GraphQL endpoint in your browser under http://localhost:4000/graphql and Apollo Studio should pop up.


It was tricky to find out how to get the documentation example to work as it needs a JavaScript promise to listen to the server.

Luckily, GitHub had the solution. I only needed to know what to look for.