Serverless for front-end developers

Front-end developers can do a lot of magic on the web nowadays, but sometimes there is still the need to handle some logic server-side. Let's do it the serverless way!

Written in Development by Núria Soriano — January 24, 2020

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, 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 to get our token!

The way the whole process works is this:

  • First, we create an OAuth app on GitHub. For the URLs, for now, you can use http://localhost:8080/ 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. 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.

Me closing the console tab right after opening it

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.

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 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 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. The framework itself is open source, the dashboard service can be used with the free plan.

So, let's get started! Create an account and follow the steps to install and login via CLI. 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.
  • 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:

    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 to make the request to GitHub so make sure to npm init to create your package.json file, and npm i axios

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

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:

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

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

Serverless dashboard function log UI

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, and learn by doing! Maybe try creating a REST API with DynamoDB, or maybe try GraphQL (we love it, if you need an introduction to GraphQL I recommend Anna's post). And if Node or AWS are not your cup of tea, there are tons of other examples!

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

Never forget old Yoda

Cover photo by chuttersnap