DEV Community

Davide Cavaliere
Davide Cavaliere

Posted on

Create Your First Lambda with Serverless, Typescript and Microgamma Part 2

This is part 2 of my serie on 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:

yarn add @microgamma/apigator@latest @microgamma/serverless-apigator@latest 
Enter fullscreen mode Exit fullscreen mode

If you wan to start straight from here you can checkout:

git clone git@github.com:microgamma/serverless-apigator-blue-print.git
Enter fullscreen mode Exit fullscreen mode

Or, if you already clone it before:

git remote update && git pull origin master
Enter fullscreen mode Exit fullscreen mode

and checkout

git checkout -b part2 my-first-apigator-part-2
Enter fullscreen mode Exit fullscreen mode

Before we start, let's build, deploy and check that everything is still working:

yarn build && yarn sls deploy --stage dev
Enter fullscreen mode Exit fullscreen mode
yarn sls invoke local -f index 
Enter fullscreen mode Exit fullscreen mode

And:

yarn sls invoke -f index --stage dev
Enter fullscreen mode Exit fullscreen mode

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:

// 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' };
  }
}

Enter fullscreen mode Exit fullscreen mode

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:

yarn build
Enter fullscreen mode Exit fullscreen mode

And call those new lambda locally

yarn sls invoke local -f index

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

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

Enter fullscreen mode Exit fullscreen mode
yarn sls invoke local -f create -d '{ "body": { "id": 3, "description": "my new ticket" } }'

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

Enter fullscreen mode Exit fullscreen mode
yarn sls invoke local -f update -d '{ "body": { "id": 1, "description": "buy beers and candies" } }'

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

Enter fullscreen mode Exit fullscreen mode
yarn sls invoke local -f delete -d '{ "path": { "id": "1" } }'

{
    "delete": "ok"
}

Enter fullscreen mode Exit fullscreen mode

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

yarn sls deploy --stage dev
Enter fullscreen mode Exit fullscreen mode

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


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

And add the following to your serverless.yml

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

Enter fullscreen mode Exit fullscreen mode

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

api keys:
  myFirstKey: XNzWxRlNEb3mJynlLi8mDamcdL6psXmx4MWHgBCe

Enter fullscreen mode Exit fullscreen mode

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.

  @Authorizer()
  public authorize(@Header('Authorization') jwt) {
    return jwt === 'jolly';
  }
Enter fullscreen mode Exit fullscreen mode

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

  @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;
  }

Enter fullscreen mode Exit fullscreen mode

After build and deploy. If try to hit our endpoint with 'POST' we'll get the following.

{
    "message": "Unauthorized"
}
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (0)