Serverless for front-end developers
Tags: serverless, beginners
Published: 2020-01-27 08:44:34

---
title: Serverless for front-end developers
published: true
date: 2020-01-24 10:00:00 UTC
tags: serverless, beginners
canonical_url: https://www.codegram.com/blog/serverless-for-front-end-developers/
---

These days you can do a lot of stuff without ever needing a backend. But there are always those moments where you need a really simple backend: authenticating users, sharing information across users and devices… Personally, being mostly a front-end developer, I can deal with coding a simple Node.js backend, but I find it very cumbersome having to deal with deployments, databases, and servers. That's why serverless can be an interesting option to add that bit of functionality to your mostly front-end based projects. Yeah, there's a server somewhere but if you don't have to deal with it, does it matter?

To use a real-life example, recently I had to build a web application that uses the GitHub API. For this, I need a token, and GitHub does not allow client-side authentication. At first, I considered using [Gatekeeper](https://github.com/prose/gatekeeper), which can be easily deployed to Heroku, but I felt this could be a good opportunity to learn serverless. So, in this post, we will learn how to handle login to a [GitHub OAuth application](https://developer.github.com/apps/building-oauth-apps/authorizing-oauth-apps/) to get our token!

The way the whole process works is this:

- First, we create an [OAuth app](https://github.com/settings/developers) on GitHub. For the URLs, for now, you can use [http://localhost:8080/](http://localhost:8001/) or wherever you are running your application.
- In your front-end application, you add a link pointing to `https://github.com/login/oauth/authorize?client_id=${clientId}&redirect_uri=${redirect_uri}`. Let's talk about this two variables:
  - `clientId`: you get it after creating the OAuth app
  - `redirect_uri`: is the authorization callback URL you set in the app settings. It's basically a route in your front-end application that will handle the token exchange.
- When the user clicks this link, they will be redirected to GitHub to accept the application.
- After the user accepts to log in to your application, GitHub will redirect them back to the `redirect_uri`, with a `code` query parameter that you can use to exchange for a token. This is the step that cannot be done client-side, so we will create a serverless function to exchange the code for our token.

Phew! That seems quite a lot of work to a simple login, but fear not, together we can do this!

First, a few concepts:

## AWS

We'll be using Amazon Web Services. Why? I tried it and I liked it so I haven't checked any other providers. So you'll need to create an [AWS account](https://aws.amazon.com/). You might be tempted to start checking out the documentation and services available but I suggest you don't (at least for now). The first time I logged into the AWS console I was so overwhelmed by the amount of information that I thought I would never be able to make anything work with that.

<video src="https://media.giphy.com/media/6h4z4b3v6XWxO/giphy.mp4" autoplay loop muted></video>

<small>Me closing the console tab right after opening it</small>

Luckily I found that you don't have to check it too often, as in the next section we will see a clearer way to access that information.

AWS offers a lot of services but we'll focus on these:

### Lambda

As you may have guessed, AWS Lambda are just functions. JavaScript functions! (Actually you can write them in a few languages but we're front-end developers so we'll stick to what we know). And you can pretty much do anything you want there: make requests to an API, get data from a database… keeping in mind that it must run within the [limitations](https://docs.aws.amazon.com/lambda/latest/dg/limits.html).

### API Gateway

But how to run these functions? Since we'll probably want to run them from your front-end application, we'll create an API (actually we won't have to do anything, the Serverless framework will), and each endpoint will trigger a Lambda.

### Parameter Store

You might need to store some environment variables that you do not want to commit to the repository, because of security reasons or just because it depends on the environment, like the OAuth id and secret. There are a few ways to do that (you can add environment variables manually to each Lambda, there's also AWS Secret Manager), but I find the [Parameter Store](https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-parameter-store.html) the easiest to deal with.

After this quick overview, let's get started!

## The Serverless framework

Setting up all these services might look like a lot of work and the whole point of this post is to use serverless to avoid complexity, and that's where [the Serverless framework](https://serverless.com/) comes into action! As its name suggests, it's a framework that helps us set up serverless services easily. We will just fill in our desired configuration on a `yml` file, and once we deploy we will be able to monitor everything easily on the [dashboard](https://dashboard.serverless.com/). The framework itself is [open source](https://github.com/serverless/serverless#licensing), the dashboard service can be used with the [free plan](https://serverless.com/pricing/).

So, let's get started! Create an account and follow [the steps to install and login via CLI](https://serverless.com/framework/docs/getting-started/). After that it's really easy to create a base project:

```
    serverless create --template aws-nodejs
```

You will see that it's just two files:

- `serverless.yml`: the configuration of your service. You will see it's full of useful comments, and you can check all the available properties [in the documentation](https://serverless.com/framework/docs/providers/aws/guide/serverless.yml/).
- `handler.js`: your actual code

For now, we will update the `serverless.yml`, add app name and organization.

We will also edit the hello world function (that's what will become our Lambda) so it becomes a `login` function! The `events` bit under the function handler creates an API Gateway endpoint to trigger our function (we set it to use the `POST` method and enable `cors`). It should look something like this:

```yml
    service: github-login
    app: github-login
    org: your-organization

    provider:
      name: aws
      runtime: nodejs10.x
      memorySize: 1024
      region: us-east-1

    functions:
      login:
        handler: login.handler
        events:
        - http:
            path: login
            method: post
            cors: true
```

Tip: Keep in mind that handlers must be named exports, it doesn't work with default exports. If you can't come up with a name for the export, the convention is to name the export `handler`.

Here is the code of our `login.js` file. We will be using [axios](https://github.com/axios/axios) to make the request to GitHub so make sure to `npm init` to create your `package.json` file, and `npm i axios`

```js
    "use strict";

    const qs = require("querystring");
    const axios = require("axios");

    function authenticate(code) {
      const data = qs.stringify({
        client_id: process.env.CLIENT_ID,
        client_secret: process.env.CLIENT_SECRET,
        code
      });

      return axios
        .post("https://github.com/login/oauth/access_token", data, {
          headers: {
            "content-length": data.length
          }
        })
        .then(({ data }) => {
          return {
            token: qs.parse(data).access_token
          };
        })
        .catch(e => ({
          error: e.message
        }));
    }

    module.exports.handler = async event => {
      const body = JSON.parse(event.body);

      const result = {
        error: null,
        token: null
      };

      if (body) {
        const { error, token } = await authenticate(body.code);

        if (error || !token) {
          result.error = error || "bad_code";
        } else {
          result.token = token;
        }
      } else {
        result.error = "no_code";
      }

      return {
        statusCode: 200,
        headers: {
          "Access-Control-Allow-Origin": "*",
          "Access-Control-Allow-Credentials": true
        },
        body: JSON.stringify(result, null, 2)
      };
    };
```

But, as we can see we need two environment variables: `CLIENT_ID` and `CLIENT_SECRET` (remember that we can get those when creating an OAuth app under [developer settings](https://github.com/settings/developers)). We know that committing environment variables into a repo is a big NO, so how can we handle this? Read on!

## Handling environment variables

This is where the parameter store will come in handy since most probably the environment variables will be different depending on the stage (OAuth apps require you to set an authorization callback URL, and this will likely be different in development and production so you will need to create two apps).

You can create new variables on the AWS console: [https://console.aws.amazon.com/systems-manager/parameters/create](https://console.aws.amazon.com/systems-manager/parameters/create?region=us-east-1#). Create the `dev` and `prod` parameters for `CLIENT_ID` and `CLIENT_SECRET` following this structure: `/app-name/stage/parameter-name` (so `/github-login/dev/clientId` and so on). You can set variables to be encrypted by selecting the `SecureString` type.

![Create parameter form UI](https://www.codegram.com/blog/serverless-for-front-end-developers/parameter-store.png)

In your `serverless.yml` file you will set the `environment` variables to be retrieved from the `custom` section, which will point to correct the parameter store variable. If you chose `SecureString` type, make sure to add `~true` when referencing it:

```yml
    provider:
      name: aws
      runtime: nodejs10.x
      stage: ${opt:stage,'dev'}
      ...
        environment:
        CLIENT_ID: ${self:custom.clientId.${self:provider.stage}}
        CLIENT_SECRET: ${self:custom.clientSecret.${self:provider.stage}}

    custom:
      clientId:
        dev: ${ssm:/github-login/dev/clientId}
        prod: ${ssm:/github-login/prod/clientId}
      clientSecret:
        dev: ${ssm:/github-login/dev/clientSecret~true}
        prod: ${ssm:/github-login/prod/clientSecret~true}
```

Keep in mind that even though the default stage is `dev`, the `self:provider.stage` bit will not get the stage correctly unless you add the `stage: ${opt:stage,'dev'}` (I got stuck a pretty long time trying to debug that!)

[This blogpost](https://www.jeremydaly.com/how-to-manage-serverless-environment-variables-per-stage/) covers how to manage environment variables per stage in a bit more detail.

Now we have all the code for our application and we are ready to deploy! If you got stuck at any part, the [full code is on GitHub](https://github.com/codegram/serverless-github-login).

## Deploying

Now our login function is ready to be deployed! You can choose where to deploy using the `stage` option (if you don't pass any stage, the default will be `dev`):

```
    serverless deploy
    serverless deploy --stage prod
```

Easy huh. After the command ends you will see all the service information, including the endpoint you will need to use from your frontend application. It should look something like `https://r4nd0m1d.execute-api.us-east-1.amazonaws.com/dev/login`. Feel free to test it using cURL or something like Postman. Even if you don't send a valid `code` in the body, you can see how it handles the error messages.

You can check the dashboard to see the function invocations and check if there are errors (the UI/UX is way more friendly than having to check the AWS console!):

![Serverless dashboard logs UI](https://www.codegram.com/blog/serverless-for-front-end-developers/logs.png)

![Serverless dashboard function log UI](https://www.codegram.com/blog/serverless-for-front-end-developers/function-log.png)

## Next steps!

I hope this was a helpful introduction to serverless! Now feel free to go back to the comfort of front-end development and use the token to start working with the GitHub API. Maybe create some data visualization with your commits per repo, or the most popular languages of your followers?

Or, if you haven't got enough of serverless, and you want to keep learning, I recommend checking out [this guide](https://serverless-stack.com/), and learn by doing! Maybe try creating a REST API with [DynamoDB](https://github.com/serverless/examples/tree/master/aws-node-rest-api-with-dynamodb), or maybe try [GraphQL](https://github.com/serverless/examples/tree/master/aws-node-graphql-api-with-dynamodb) (we love it, if you need an introduction to GraphQL I recommend [Anna's post](https://www.codegram.com/blog/graphql-101)). And if Node or AWS are not your cup of tea, there are tons of [other examples](https://github.com/serverless/examples)!

But of course, the best thing you can do now is to relax for a while, you've earned it!

<video src="https://media.giphy.com/media/26tn8zNgVmit475RK/giphy.mp4" autoplay loop muted></video>

<small>Never forget old Yoda</small>

* * *

Cover photo by [chuttersnap](https://unsplash.com/@chuttersnap?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)