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

Today we'll be making a REST API using [Go Fiber](https://gofiber.io/). 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

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

```go
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\](https://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`

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

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

```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:

```go
...
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:

```json
 [
  ...
  {
    "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`

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

```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:

```go
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 &gt; Lambda
    
3. Click on 'Create Function'
    
    ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1675348546273/3fdd2cbb-e12b-421b-91d7-71e6554a55a9.png align="center")
    
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'.
    
    ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1675348647800/1453a19c-d21c-4cb7-8427-22f9deddeaff.png align="center")
    
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'.
    
    ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1675348943169/5f32cbbc-3025-4583-b173-83799eacfe36.png align="center")
    
    ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1675348971782/9f925c33-e297-42e8-b179-b35fcf61efc4.png align="center")
    
7. Test the function.
    
    ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1675349045071/f3fd33c6-c8be-4578-8864-b2522d14723e.png align="center")
    

### 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'
    
    ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1675349197007/639cf4a7-cc05-488c-b65c-2c8d61f90fe9.png align="center")
    
2. Select 'API Gateway' and select the intent as 'Create a new API'. Select 'REST API' and security as 'Open'. Click on 'Add'.
    
    ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1675349333888/480b6f43-6097-46e4-8edb-2ca2b45b33ad.png align="center")
    

### Configuring API Gateway

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1675349491456/10922e18-2115-450e-83f1-fcd58de96ed0.png align="center")

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 &gt; Create Method. Choose ANY as the method.
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1675349649817/4908b3ee-111e-4d56-9f4c-9b2c5cd21dab.png align="left")

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

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1675349704156/9e5bd0d3-4b85-4390-bb10-f94a08cf1c0b.png align="center")

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

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1675349718594/de4dec9e-b6de-4ba7-b2ae-cbb2e9b05df3.png align="center")

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

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1675349742820/160596b6-c0df-4a4d-b3b1-8428fbff9445.png align="center")

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

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1675349765994/ef6672fb-8eff-4980-a434-82ace3df2f8b.png align="center")

1. Check the API by clicking on the 'Invoke URL'
    
    ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1675350285930/b44d3c75-91a2-4435-b465-cb693f08e1cb.png align="center")
    
    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 &gt; Create a new resource, and configure it for the `/users` endpoint as shown. Click on Create Resource.
    
    ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1675350399993/b33d7e15-f338-4ded-a568-34c0e942b54b.png align="center")
    
3. Then select the `/users` resource, go to Actions &gt; Create a method, and select GET. Configure as we did before. The result should look like this.
    
    ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1675350479882/a58ce66b-d060-4fb0-b978-81fb3bd88d6d.png align="center")
    
4. Go to Actions &gt; Deploy API. Click on the 'Invoke URL' and you should see the data of the fake users on your screen.
    
    ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1675350861858/b71df9de-6317-460d-8dff-364c077f787c.png align="center")
    

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.
