Create Your First Lambda with Serverless, Typescript and Microgamma Part 2
Tags: aws, typescript, serverless, microservices
Published: 2020-01-26 08:06:14

This is part 2 of my serie on [Microgamma](//github.com/microgamma/microgamma).

> TLTR;
> In part 1 we saw how to set up a project with typescript, microgamma and serverless and how to deploy a very simple endpoint


In this post we will:
1 - create our first REST api 
2 - make it private adding an api-key
3 - secure our lambdas with an authorizer function



If you followed the steps in the first part then make sure to update:

```bash
yarn add @microgamma/apigator@latest @microgamma/serverless-apigator@latest 
```

If you wan to start straight from here you can checkout:
```bash
git clone git@github.com:microgamma/serverless-apigator-blue-print.git
```
Or, if you already clone it before: 

```bash
git remote update && git pull origin master
``` 

and checkout
```bash
git checkout -b part2 my-first-apigator-part-2
```

Before we start, let's build, deploy and check that everything is still working:
```bash
yarn build && yarn sls deploy --stage dev
```

```bash
yarn sls invoke local -f index 
```
And: 

```bash
yarn sls invoke -f index --stage dev
``` 

## A Tickets service
Let's now make our service a bit more useful. Let's suppose that our endpoint will handle the requests for interacting with Tickets.

So we have:

- `GET /` retrive all tickets.
- `GET /{id}` retrieve a ticket by id.
- `POST /` creates a new ticket (ticket is passed through request's _body_).
- `PUT /` updates an existing ticket (ticket is passed through request's _body_).
- `DELETE /{id}` deletes a ticket.

This is how our service will look like:

```typescript
// my-first-service.ts

import { Body, Endpoint, Lambda, Path } from '@microgamma/apigator';
import { Injectable } from '@microgamma/digator';

@Endpoint({
  cors: true,
  name: 'my-first-service',
  private: false
})
@Injectable()
export class MyFirstService {

  private static tickets = [{
    id: 1,
    description: 'Buy beers'
  }, {
    id: 2,
    description: 'send email'
  }];

  private lastIndex = 2;

  @Lambda({
    method: 'GET',
    path: '/',
    description: 'get all tickets'
  })
  public index() {
    return MyFirstService.tickets;
  }

  @Lambda({
    method: 'GET',
    path: '/{id}',
    description: 'find a ticket by id'
  })
  public findById(@Path('id') ticketId) {

    const found = MyFirstService.tickets.find((ticket) => ticket.id === Number(ticketId));

    if (!found) {
      throw new Error('[404] not found');
    }

    return found;
  }

  @Lambda({
    method: 'POST',
    path: '/',
    description: 'create a new ticket'
  })
  public create(@Body() ticket) {
    ticket.id = ++this.lastIndex;
    MyFirstService.tickets.push(ticket);
    return ticket;
  }

  @Lambda({
    method: 'PUT',
    path: '/',
    description: 'updates a ticket'
  })
  public update(@Body() ticket) {
    const found = MyFirstService.tickets.find((t) => t.id === Number(ticket.id));

    if (!found) {
      throw new Error('[404] not found');
    }

    Object.assign(found, ticket);
    return found;
  }

  @Lambda({
    method: 'DELETE',
    path: '/{id}',
    description: 'delete a ticket by id'
  })
  public delete(@Path('id') ticketId) {
    const found = MyFirstService.tickets.find((t) => t.id === Number(ticketId));

    if (!found) {
      throw new Error('[404] not found');
    }

    const indexOf = MyFirstService.tickets.indexOf(found);

    MyFirstService.tickets.splice(indexOf, 1);

    return { delete: 'ok' };
  }
}

```

In the above snippet we can notice two new decorators: `@Path('id')` which retrives `id` from the _path parameters_ and `@Body()` that retrives the request's _body_.


Let's build again:
```bash
yarn build
```

And call those new lambda locally

```bash
yarn sls invoke local -f index

[
    {
        "id": 1,
        "description": "Buy beers"
    },
    {
        "id": 2,
        "description": "send email"
    }
]
```
```bash
yarn sls invoke local -f findById -d '{ "path": { "id": "1" } }'

{
    "id": 1,
    "description": "Buy beers"
}

```
```bash
yarn sls invoke local -f create -d '{ "body": { "id": 3, "description": "my new ticket" } }'

{
    "id": 3,
    "description": "my new ticket"
}

```
```bash
yarn sls invoke local -f update -d '{ "body": { "id": 1, "description": "buy beers and candies" } }'

{
    "id": 1,
    "description": "buy beers and candies"
}

```
```bash
yarn sls invoke local -f delete -d '{ "path": { "id": "1" } }'

{
    "delete": "ok"
}

```
Every call should work just fine, so let's try to deploy.

```bash
yarn sls deploy --stage dev
```

We can call again the same commands as above without `local` and adding `--stage dev`

Or we could use postman for a quick e2e test.

## Protect your Lambdas with an Api Key

In order to achieve this you just need to change the `@Endpoint` decorator as follows
```typescript

@Endpoint({
  cors: true,
  name: 'my-first-service',
  private: true
})
@Injectable()
export class MyFirstService {
```

And add the following to your `serverless.yml`


```diff
--- a/serverless.yml
+++ b/serverless.yml
@@ -2,6 +2,8 @@ service: my-first-apigator # NOTE: update this with your service name
 
 provider:
   name: aws
+  apiKeys:
+    - myFirstKey
   runtime: nodejs12.x
   stage: dev
   region: eu-west-2

```

Build and deploy. We should see something like the following in our terminal.

```bash
api keys:
  myFirstKey: XNzWxRlNEb3mJynlLi8mDamcdL6psXmx4MWHgBCe

```

So now if we try to hit our endpoint we'll get a `403 Forbidden` error.
Adding the above api key to our headers will let us reach our data.

> Bear in mind that the header name should be: `x-api-key`

## Add an authorizer function
Having a lambda that is called by another lambda to check whether the request comes from authorized client it a common pattern. It's most simple implementation it will check the validity of a jwt token passed through the headers of the request. So that if for example the used is authenticated it would have provided a valid jwt token.
For now we're gonna implement something way simpler but still a valid point in order to show how to do it in Microgamma.

First of all we add an `authorize` method to our service.
```typescript
  @Authorizer()
  public authorize(@Header('Authorization') jwt) {
    return jwt === 'jolly';
  }
```
Decorated with `@Authorizer` which tells serverless that this is an authorizing function.

We also use here `@Header('Authorization')` which, alike `@Path`, extracts from the headers the field `Authorization`.

Then we need to set this authorizer into the lambdas we want to protect. I.e. the `create` lambda
```typescript
  @Lambda({
    method: 'POST',
    path: '/',
    description: 'create a new ticket',
    authorizer: 'authorize'
  })
  public create(@Body() ticket) {
    ticket.id = ++this.lastIndex;
    MyFirstService.tickets.push(ticket);
    return ticket;
  }

```

After build and deploy. If try to hit our endpoint with 'POST' we'll get the following.
```json
{
    "message": "Unauthorized"
}
```
So now we need to provide the `Authorization` header to our request.

That's all folks. Hope you enjoyed and thanks for reading. Any feedback is very welcome and stay tuned for more @microgamma sugar.