Building a Serverless REST API with Go, AWS Lambda, and API Gateway

Building a Serverless REST API with Go, AWS Lambda, and API Gateway

·

6 min read

Today we'll be making a REST API using Go Fiber. The API returns dummy data for different entities like users, posts, employees, etc., that can be used for development and testing purposes.

Prerequisites:

  • Go installed on your system

  • AWS account

Setting up the Project

mkdir dummy-data
cd dummy-data
go mod init github.com/omeiirr/dummy-data
go get github.com/gofiber/fiber/v2

Create a file main.go in the root directory.

package main

import (
    "github.com/gofiber/fiber/v2"
)

func main() {
    app := fiber.New()

    app.Get("/", func(c *fiber.Ctx) error {
        return c.SendString("Server is running normally")
    })

    app.Listen(":3000")
}

Now start the server with go run . in the terminal and visit localhost:3000 in your browser. You should see "Server is running normally".

Generating Fake Data

We'll use the [faker](github.com/jaswdr/faker) library to generate the fake data. Install this as a dependency with go get github.com/jaswdr/faker

Let us now define a struct for a User. Create a file models/user.go

package models

type User struct {
    ID       int    `json:"id"`
    Name     string `json:"name"`
    Username string `json:"username"`
    Email    string `json:"email"`
}

We also need a helper function that will generate n number of fake users. Create a file helpers/generate_users.go

package helpers

import (
    "github.com/jaswdr/faker"
    "github.com/omeiirr/dummy-data/models"
)

func GenerateUsers(n int) []models.User {
    fake := faker.New()

    var users []models.User
    for i := 0; i < n; i++ {
        user := models.User{
            ID:       fake.Int(),
            Name:     fake.Person().FirstName(),
            Username: fake.Internet().User(),
            Email:    fake.Person().Contact().Email,
        }
        users = append(users, user)
    }

    return users
}

Lastly, we will create a handler function that will be called on the API endpoint, and will return fake users in JSON format. Create a file handlers/return_users.go

package handlers

import (
    "github.com/gofiber/fiber/v2"
    "github.com/omeiirr/dummy-data-api/helpers"
)

func ReturnUsers(c *fiber.Ctx) error {
    users := helpers.GenerateUsers(5)
    return c.JSON(users)
}

Now we need to call this function in our main.go file at the endpoint users. We can also move the HealthCheck function to handlers to keep our code modular and concise.

Our main.go file looks like this:

...
func main() {
    app := fiber.New()

    app.Get("/", handlers.HealthCheck)
    app.Get("/users", handlers.ReturnUsers)

    app.Listen(":3000")
}

Let's check if this works as expected or not. Restart the development server with go run . and visit localhost:3000/users in your browser. You should see data of 5 users like so:

 [
  ...
  {
    "id": 2665926480750013400,
    "name": "Taylor",
    "username": "garth.hackett",
    "email": "marcelle@hte.com"
  },
  {
    "id": 5321523606392798000,
    "name": "Roxane",
    "username": "lourdes.runolfsdottir",
    "email": "silas.lemke@uay.com"
  },
  ...
]

Running Code on Lambda

Great! The API is working as expected on localhost. Now we need to put it on AWS Lambda to execute it as a serverless function. But for that, we need to make certain modifications to our code.

main.go

var fiberLambda *fiberadapter.FiberLambda

fiberLambda = fiberadapter.New(app)
lambda.Start(Handler)

func Handler(ctx context.Context, request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
    return fiberLambda.ProxyWithContext(ctx, request)
}

We also need this function to run on both localhost (for development) and Lambda. We will create a helper function to determine whether the function is running in a Lambda environment or not. We can do so by checking the existence of an environment variable LAMBDA_TASK_ROOT , that will only be present in a Lambda execution environment.

helpers/is_lambda.go

package helpers

import "os"

func IsLambda() bool {
    if lambdaTaskRoot := os.Getenv("LAMBDA_TASK_ROOT"); lambdaTaskRoot != "" {
        return true
    }
    return false
}

This is how the main function looks now:

package main

import (
    "context"

    "github.com/aws/aws-lambda-go/events"
    "github.com/aws/aws-lambda-go/lambda"
    fiberadapter "github.com/awslabs/aws-lambda-go-api-proxy/fiber"
    "github.com/gofiber/fiber/v2"
    "github.com/omeiirr/dummy-data/handlers"
    "github.com/omeiirr/dummy-data/helpers"
)

var fiberLambda *fiberadapter.FiberLambda

func main() {
    app := fiber.New()

    app.Get("/", handlers.HealthCheck)
    app.Get("/users", handlers.ReturnUsers)

    if helpers.IsLambda() {
        fiberLambda = fiberadapter.New(app)
        lambda.Start(Handler)
    } else {
        app.Listen(":3000")
    }

}

func Handler(ctx context.Context, request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
    return fiberLambda.ProxyWithContext(ctx, request)
}

Creating Executable File

We need to create an executable so it can be deployed on Lambda.

Create the executable file with GOOS=linux GOARCH=amd64 go build -o main

If you're working on a non-Linux device, you need to specify the GOOS and GOARCH variables like so because the Lambda function runs on these specifications. These variables tell the Go compiler to create the executable which is compatible with such specs. This is known as cross-compiling.

An executable with the main is created in the root directory. Now zip it before uploading it to Lambda using the command zip main.zip main

Creating Lambda Function

  1. Log in to your AWS account

  2. Go to Services > Lambda

  3. Click on 'Create Function'

  4. Choose the option 'Author from Scratch', name the function, and select 'Go 1.x' as the runtime. Leave the other options as the default. Click on 'Create Function'.

  5. Choose the option to upload as a .zip file and upload the zip file we created earlier.

  6. Change the name of Handler in 'Runtime settings' from 'hello' to 'main'.

  7. Test the function.

Connecting to API Gateway

Now that our Lambda function is working properly, we need to connect it to Amazon API Gateway, so it can be accessed through an endpoint URL.

  1. Select 'Add Trigger'

  2. Select 'API Gateway' and select the intent as 'Create a new API'. Select 'REST API' and security as 'Open'. Click on 'Add'.

Configuring API Gateway

If we try to access the endpoint, it shows: Cannot GET /dummy-data-api

We need to make some minor configurations on our API Gateway. Click on the API name.

  1. Select the root (/) resource, go to Actions > Create Method. Choose ANY as the method.

  1. Configure the following for the method and then click on Save.

  1. Grant permission to API Gateway to invoke the Lambda function.

  1. Again go to Actions, then click on Deploy API

  1. Create a new stage for the deployment v1 and click on Deploy.

  1. Check the API by clicking on the 'Invoke URL'

    You should see Server is running normally in the browser.

  2. Now we need to create a similar method for accessing the /users endpoint.
    Go to the 'Resources' in the left sidebar, click on Actions > Create a new resource, and configure it for the /users endpoint as shown. Click on Create Resource.

  3. Then select the /users resource, go to Actions > Create a method, and select GET. Configure as we did before. The result should look like this.

  4. Go to Actions > Deploy API. Click on the 'Invoke URL' and you should see the data of the fake users on your screen.

Perfect! We have created a RESTful API, deployed it to AWS Lambda, and connected it to API Gateway, so it can be accessed using the endpoint anywhere.

The source code for this tutorial can be accessed [here]().

Scope for Improvement

This tutorial is a very basic demo of how to create REST APIs using Go and connecting them to different AWS Services.

You can implement more features like:

  • Adding more resources like posts, items, employees, etc.

  • Authorization using token

  • Pagination

I hope you found this helpful. Feel free to like this article and share it with your friends.