Serverless Golang API with AWS Lambda

Serverless Golang API with AWS Lambda

AWS has announced few days ago, Go as supported language for AWS Lambda. So, I got my hand dirty and I made a Serverless Golang Lambda Function to discover new Movies by genres, I went even further and created a Frontend in top of my API with Angular 5.

Note: The full source code for this application can be found on GitHub



To get started, install the dependencies below:

1
2
go get github.com/aws/aws-lambda-go/lambda # for handler registration
go get github.com/stretchr/testify # for unit tests

Create a main.go file with the following code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
package main

import (
"encoding/json"
"errors"
"fmt"
"net/http"
"os"
"strconv"

"github.com/aws/aws-lambda-go/lambda"
)

var (
API_KEY = os.Getenv("API_KEY")
ErrorBackend = errors.New("Something went wrong")
)

type Request struct {
ID int `json:"id"`
}

type MovieDBResponse struct {
Movies []Movie `json:"results"`
}

type Movie struct {
Title string `json:"title"`
Description string `json:"overview"`
Cover string `json:"poster_path"`
ReleaseDate string `json:"release_date"`
}

func Handler(request Request) ([]Movie, error) {
url := fmt.Sprintf("https://api.themoviedb.org/3/discover/movie?api_key=%s", API_KEY)

client := &http.Client{}

req, err := http.NewRequest("GET", url, nil)
if err != nil {
return []Movie{}, ErrorBackend
}

if request.ID > 0 {
q := req.URL.Query()
q.Add("with_genres", strconv.Itoa(request.ID))
req.URL.RawQuery = q.Encode()
}

resp, err := client.Do(req)
if err != nil {
return []Movie{}, ErrorBackend
}
defer resp.Body.Close()

var data MovieDBResponse
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
return []Movie{}, ErrorBackend
}

return data.Movies, nil
}

func main() {
lambda.Start(Handler)
}

The handler function takes as a parameter the movie genre ID then query the TMDb API – Awesome free API for Movies and TV Shows – to get list of movies. I registred the handler using the lambda.Start() method.

To test our handler before deploying it, we can create a basic Unit Test:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestHandler(t *testing.T) {
movies, err := Handler(Request{
ID: 28,
})
assert.IsType(t, nil, err)
assert.NotEqual(t, 0, len(movies))
}

Issue the following command to run the test:



Next, build an executable binary for Linux:

1
GOOS=linux go build -o main main.go

Zip it up into a deployment package:

1
zip deployment.zip main

Use the AWS CLI to create a new Lambda Function:

1
2
3
4
5
6
7
aws lambda create-function \
--region us-east-1 \
--function-name DiscoverMovies \
--zip-file fileb://./deployment.zip \
--runtime go1.x \
--role arn:aws:iam::<account-id>:role/<role> \
--handler main

Note: substitute role flag with your own IAM role.



Sign in to the AWS Management Console, and navigate to Lambda Dashboard, you should see your lambda function has been created:



Set TMDb API KEY (Sign up for an account) as environment variable:



Create a new test event:



Upon successful execution, view results in the console:



To provide the HTTPS frontend for our API, let’s add API Gateway as a trigger to the function:



Deployment:



Now, if you point your favorite browser to the Invoke URL:



Congratulations ! you have created your first Lambda function in Go.



Let’s build a quick UI in top of the API with Angular 5. Create an Angular project from scratch using Angular CLI. Then, generate a new Service to calls the API Gateway URL:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import { Observable } from 'rxjs/Rx';
import 'rxjs/add/operator/map';
import { environment } from '@env/environment';

@Injectable()
export class MovieService {
private baseUrl: string = environment.api;

constructor(private http: Http){}

public getMovies(id?: number){
return this.http
.post(`${this.baseUrl}`, {ID: id})
.map(res => {
return res.json()
})
}
}

In the main component iterate over the API response:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<section class="container">
<div class="row">
<div class="col-md-12">
<button *ngFor="let genre of genres" (click)="getMoviesByGenre(genre.id)" class="btn btn-secondary"></button>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<table class="table table-hover">
<thead>
<tr>
<th>Poster</th>
<th width="20%">Title</th>
<th>Description</th>
<th>Release Date</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let movie of movies">
<td>
<img src="https://image.tmdb.org/t/p/w500/" class="cover">
</td>
<td>
<span class="title"></span>
</td>
<td>
<p class="description"></p>
</td>
<td>
<span class="date"></span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</section>

Note: the full code is in GitHub.

Generate production grade artifacts:

1
ng build --env=prod


The build artifacts will be stored in the dist/ directory

Next, create an S3 bucket with AWS CLI:

1
aws s3 mb s3://discover-movies

Upload the build artifacts to the bucket:

1
aws s3 cp dist/ s3://discover-movies --recursive --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers

Finally, turns website hosting on for your bucket:

1
aws s3 website s3://discover-movies --index-document index.html

If you point your browser to the S3 Bucket URL, you should be happy:



Drop your comments, feedback, or suggestions below — or connect with me directly on Twitter @mlabouardy.

Comments

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×