Serverless GraphQL Server: Deploying to Netlify Functions, 3 ways

ekafyi

Eka

Posted on April 9, 2021

Serverless GraphQL Server: Deploying to Netlify Functions, 3 ways

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 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 and Glitch 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.


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.

{
  "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"
  }
}
Enter fullscreen mode Exit fullscreen mode
  • Dependencies:
    • apollo-server-lambda
      • We use apollo-server-lambda instead of apollo-server above. 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.

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!
Enter fullscreen mode Exit fullscreen mode

netlify.toml

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

[build]
  command = ""
  functions = ""
  publish = ""
Enter fullscreen mode Exit fullscreen mode

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.

. /
├── functions/
│   └── graphql.js
├── static/
│   └── index.js # can be empty but has to exist
├── netlify.toml
└── package.json
Enter fullscreen mode Exit fullscreen mode

netlify.toml:

[build]
  command = "yarn"
  functions = "functions"
  publish = "static"
Enter fullscreen mode Exit fullscreen mode

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 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.

. /
├── src/ # rename from functions to src
│   └── graphql.js
├── static/
│   └── index.js
├── netlify.toml
└── package.json
Enter fullscreen mode Exit fullscreen mode

Then change the command in netlify.toml:

[build]
- command = "yarn"
+ command = "yarn build"
  functions = "functions"
  publish = "static"
Enter fullscreen mode Exit fullscreen mode

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 for the build step. Install it and add these commands to package.json.

npm install --save-dev netlify-lambda
# or
yarn add -D netlify-lambda
Enter fullscreen mode Exit fullscreen mode
{
  "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
}
Enter fullscreen mode Exit fullscreen mode

Notes:

  • We add two commands, serve (optional; you may use Netlify 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.

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

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.

npm install --save-dev @babel/preset-typescript typescript @types/node @types/aws-lambda
Enter fullscreen mode Exit fullscreen mode

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

.babelrc

{
    "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"
    ]
}
Enter fullscreen mode Exit fullscreen mode

tsconfig.json

{
    "compilerOptions": {
        "target": "es5",
        "module": "commonjs",
        "moduleResolution": "node",
        "outDir": "./functions",
        "strict": false,
    },
    "exclude": [
        "node_modules",
        "functions"
    ]
}
Enter fullscreen mode Exit fullscreen mode

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.

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
Enter fullscreen mode Exit fullscreen mode

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

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

GraphQL IDE playground

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!

Serverless GraphQL Server on Netlify Functions, 3 Ways

Sample repo for my DEV.to post

Does what it says on the tin...

  • no-build — graphql server function without build step
  • with-build — graphql server function with netlify-lambda build
  • with-ts — graphql server function with netlify-lambda build + TypeScript

Quick Start

Deploy with Netlify

If deploying from this repo, make sure you set the base directory to the directory you want to use (eg. no-build). See: https://docs.netlify.com/configure-builds/get-started/#definitions

Else

Or you can clone this repo and start a Git repo manually/locally from the directory you want to use.

git clone https://github.com/ekafyi/hello-graphql-server.git

cd hello-graphql-server/no-build

npm install

git init
Enter fullscreen mode Exit fullscreen mode

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


References

💖 💪 🙅 🚩
ekafyi
Eka

Posted on April 9, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related