Serverless GraphQL Server: Deploying to Netlify Functions, 3 ways
Tags: graphql, netlifyfunctions, apollo
Published: 2021-04-09 17:20:39

As a detour from my attempt at learning GraphQL resolvers and data sources, I'm going to try... *deploying a server without data sources*. 😬

There are multiple options for hosting our server.
- Apollo Server [official documentation](https://www.apollographql.com/docs/apollo-server/deployment) has sections on deploying to Heroku, Lambda, Netlify, and Azure Functions. Firebase Cloud Functions is in the same category. All of them (at the time of writing) have free tiers.
- For demos or starter templates, web IDEs with Node server like [CodeSandbox](https://codesandbox.io/) and [Glitch](https://glitch.com/) can get you started quickly with _near-zero configuration_. These are not ideal for production use if you are on a free account, since the server goes to sleep after x minutes of inactivity.
- Finally, you can always self-host your server if you have the resources and knowledge.

This post focuses on the first option, which is a good choice for those who have no/little experience deploying servers but want a robust, scalable solution.

I'm going to discuss 3 ways to deploy our server on Netlify Functions. They are essentially similar and achieve the same objective, but with slight variations suited for different needs.

If you've never used or (?)heard of Netlify Functions at all, here is a post I wrote last year about serverless and Netlify Functions in general.

{% link https://dev.to/ekafyi/getting-started-with-netlify-functions-part-1-zero-config-setup-and-writing-our-first-functions-1i5b %}

---

**Table of Contents**

- Basic code and dependencies
- Option 1: No build
- Option 2: With build
- Option 3: With TypeScript
- Connecting Git repo to Netlify

*TL;DR?* Check out the sample repo at the end of this post.

---

## Basic code and dependencies

Let's start with our basic project setup.

#### package.json

Like any JS app, we need a `package.json` file for our project information, commands, and dependencies. We'll get to the commands later—now let's look at the dependencies.

```js
{
  "name": "my-server",
  "private": true,
  "scripts": {},
  "dependencies": {
    "apollo-server-lambda": "^2.22.1",
    "graphql": "^15.5.0"
  },
  "devDependencies": {
    "encoding": "^0.1.13",
    "netlify-cli": "^3.13.7"
  }
}
```

- Dependencies:
  - apollo-server-lambda
     - We use `apollo-server-lambda` instead of `apollo-server` above. [apollo-server-lambda](https://www.npmjs.com/package/apollo-server-lambda) is Apollo Server's AWS Lambda integration, and Netlify Functions uses AWS Lambda under the hood.
  - graphql *(...but of course)*
- Dev dependencies:
  - encoding
  - netlify-cli

Our server app does not directly use the dev dependencies, but rather they enable Netlify to build the code. At the time of writing, without these, we'll get a build error `bash: yarn: command not found`.

#### graphql.js

Here is our **server code**. It has to be named `graphql.js`. To keep things simple, we use the example from Apollo documentation.

```js
const { ApolloServer, gql } = require('apollo-server-lambda');

const typeDefs = gql`
  type Query {
    hello: String
  }
`;

const server = new ApolloServer({
  typeDefs,
  mocks: true,
  playground: true // enable GraphQL Playground IDE on prod env
});

exports.handler = server.createHandler(); // Don't forget to add this!
```

#### netlify.toml

Next, we need a **Netlify build configuration** file called `netlify.toml`. Leave the values blank for now.

```yaml
[build]
  command = ""
  functions = ""
  publish = ""
```

We can optionally add more options and/or configure additional environments, but these are the least we should have. 

These options are also available from the Netlify web UI settings. In case of conflicting values between the configuration file and the Netlify UI settings, the config file wins.

## Option 1: No build

This is the most simple setup we could possibly have.

```bash
. /
├── functions/
│   └── graphql.js
├── static/
│   └── index.js # can be empty but has to exist
├── netlify.toml
└── package.json
```

netlify.toml:

```yaml
[build]
  command = "yarn"
  functions = "functions"
  publish = "static"
```

Our `package.json` file is identical to the basic example above.

Notes:
- The only command that we run on build is `yarn`, ie. install the dependencies.
- `functions = "functions"` means our function code exists in a directory called `functions`. You can use any name, eg. `functions = "my-functions"`, just make sure the function code exists there.
- `publish = "static"` means our static build files are in a directory called `static`. We only use the serverless functions here and we are not serving any web page, but this directory is required. Add an empty `index.js` file there. Again, you can replace this with any directory name.
- We simply serve our code; it is not compiled. So we have to use CommonJS syntax like regular Node.js apps.

Pros:
- Simple and straightforward. Ideal for simple servers.
- Faster build time. The free plan gives us 300 build minutes per month, so faster buid time means saving money.

Cons:
- ~~Limited capability, eg. can't use ES6 imports or anything that requires compiling/transpiling.~~

**EDIT:** Netlify recently [announced an upcoming new bundler](https://www.netlify.com/blog/2021/04/02/modern-faster-netlify-functions/) that—among other features—supports using ES modules syntax. If you just want to use `import`, optional chaining, etc, you most likely won't need `netlify-lambda build`. It is opt-in now and will be launched as public default next May. Unfortunately I don't have time to play with it now... let me know if you have tried it! 😁

## Option 2: With build

First, rename the directory containing our server file (`graphql.js`) from `functions` to `src`.

```bash
. /
├── src/ # rename from functions to src
│   └── graphql.js
├── static/
│   └── index.js
├── netlify.toml
└── package.json
```

Then change the command in `netlify.toml`:

```diff
[build]
- command = "yarn"
+ command = "yarn build"
  functions = "functions"
  publish = "static"
```

We *don't* change the functions directory here, the build/destination directory is still `functions`, while our server source code now lives in `src`.

We are using [netlify-lambda](https://github.com/netlify/netlify-lambda) for the build step. Install it and add these commands to `package.json`.

```bash
npm install --save-dev netlify-lambda
# or
yarn add -D netlify-lambda
```

```diff
{
  "scripts": {
+   "start": "netlify-lambda serve src",
+   "build": "netlify-lambda build src"
  },
  "devDependencies": {
    "encoding": "^0.1.13",
    "netlify-cli": "^3.13.7",
+   "netlify-lambda": "^2.0.3"
  },
  // ... no change to rest of file
}
```

Notes:
- We add two commands, `serve` (optional; you may use [Netlify Dev](https://www.netlify.com/products/dev/) instead) and `build`. In both commands, we tell netlify-lambda to build from the `src` directory—where our function code is.
- netlify-lambda knows where the source files are, but how does it know _where_ to build the code a.k.a. the destination folder? It reads our `netlify.toml` file! 🎩

Finally, modify our server code to use ES6 `import` instead of the Node/CommonJS `require`.

```diff
// src/graphql.js
- const { ApolloServer, gql } = require('apollo-server-lambda');
+ import { ApolloServer, gql } from "apollo-server-lambda";
```

Pros:
- We can have more complex Lambda functions with transpiled modern features, imports, etc.

Cons:
- As a general rule, more dependencies = longer build and higher possibility of errors or incompatibilities. Only use the build step if necessary.

## Option 3: With TypeScript

We can use TypeScript with Netlify functions to take advantage of its type-checking and Intellisense features.

First, install the dependencies.

```bash
npm install --save-dev @babel/preset-typescript typescript @types/node @types/aws-lambda
```

Then create a Babel config file and a TypeScript config file in our root directory.

.babelrc

```js
{
	"presets": [
		"@babel/preset-typescript",
		[
			"@babel/preset-env",
			{
				"targets": {
					"node": true
				}
			}
		]
	],
	"plugins": [
		"@babel/plugin-proposal-class-properties",
		"@babel/plugin-transform-object-assign",
		"@babel/plugin-proposal-object-rest-spread"
	]
}
```

tsconfig.json

```js
{
	"compilerOptions": {
		"target": "es5",
		"module": "commonjs",
		"moduleResolution": "node",
		"outDir": "./functions",
		"strict": false,
	},
	"exclude": [
		"node_modules",
		"functions"
	]
}
```

Your tsconfig file does not have to look like this. Just make sure the `outDir` value matches the `functions` value in our `netlify.toml`.

Rename our server file from `graphql.js` to `graphql.ts`. 

This part is optional: I add a resolver function with a custom function using TS syntax. Our `hello` query now takes an optional argument `name`, which the server will *SHOUT!!!!* back to us.

```diff
import { ApolloServer, gql } from "apollo-server-lambda";

+ const shout = (msg: string) => {
+   return `${msg.toUpperCase()}!!!!`
+ }

const typeDefs = gql`
  type Query {
+   hello(name: String): String
  }
`;

const resolvers = {
  Query: {
+   hello: (_parent, args) => shout(`Hello ${args.name || 'serverless server'}`),
  },
};

// ... no change to the rest of code
```

Pros:
- Same as Option 2, plus TypeScript 

Cons:
- Same as Option 2

## Connecting Git repo to Netlify

Sign up for a free Netlify account if you haven't got one. Push your code to a Git repository (Github, Gitlab, or Bitbucket).

Go to Netlify Dashboard on https://app.netlify.com and choose *"New site from Git"*. *Continuous Deployment* is enabled by default, so Netlify deploys your site every time you push to your repo.

Once deployed, you can check the build process in the *Deploys* section on `https://app.netlify.com/sites/YOUR-SITE-NAME/deploys`. If your build fails (😿), the logs are available there. 

When the build completes successfully, your serverless server function will appear in the *Functions* section under the name `graphql`.

![Netlify dashboard - functions section](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/22so5au35tg602pgzf0b.png)

Click to view its details and endpoint URL. If we enabled `playground` config in our server code above, we can interact with our server directly by accessing GraphQL IDE from the endpoint URL.

![Netlify dashboard - function log](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/grms5hz3e250hmefjupa.png)

![GraphQL IDE playground](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ytaajjj41mvor619m8mw.png)

You can do various customizations (turn off automatic deploys, customize repo branch and base directory, and more) from *Site settings > Build & deploy* on `https://app.netlify.com/sites/YOUR-SITE-NAME/settings/deploys`.

---

## Conclusion

Serverless cloud functions services like Netlify Functions enable us to get an API server live and running with no charge (to begin with), with little infrastructure and devOps knowledge.

You can find all the code above in this repo. Fork it and build something fun!

{% github ekafyi/hello-graphql-server %}

Thank you for reading, and stay tuned for more beginner GraphQL learning attempts. 🤞🏽

---

References
- https://github.com/netlify/netlify-lambda
- https://docs.netlify.com/site-deploys/overview/
- https://docs.netlify.com/configure-builds/file-based-configuration