Search code examples
expressaws-lambdaazure-functionsserverlessaws-serverless

Express.js app serverless, using Lambda or Functions - a good idea?


Disclaimer: I admit this is more of a broad-ish best practice question, rather then a specific programming issue, however, I believe the SO bunch is the best audience for it. I am aware of this similar question (Should I be using Express.js in a Serverless app?), but the answers don't seem to answer mine.

I want to move an Express.js from AWS Lightsail/EC2 to serverless for typical reasons, and Lambda is my weapon of choice. However, a whole framework together with an app on top of it may be a bit hefty to be still considered a function and so possibly unsuitable to be run as such on AWS Lambda, or Google/Azure Functions. While I'm convinced it is doable, is it a good idea? Wouldn't this setup kill efficiency and complicate handling things such as sessions states, ultimately defeat the purpose of serverless functions?


Solution

  • The answer to your question depends upon your current and future needs. I think you should always plan ahead and make sure that the current infrastructure that you will implement can be upgradable for future needs.

    You should ask yourself those questions:

    • In the future do I want to have some websocket connection?
    • Do I need any proxy in my request routing?
    • How big will become my application over time?
    • Which AWS Service will I expect to use in the future

    Scalability

    Using Express.js in a lambda is not a good idea for many reasons:

    1. You will pay more for the execution of your Lambda because it will take more time to run and maybe more memory
    2. Latency is higher
    3. Doing a small modification means redeploying all your application code on 1 lambda so only 1 point of failure.
    4. Normally overtime your application's code base will grow as you add more features. The maintenance of that monolithic repo will be a pain in the ass and you will deploy less than you want because of the bugs you might encounter.

    Cost effectiveness

    Express.js on Lambda is more expensive because you need to proxy any method into your lambda using a API Gateway REST API rather then using API Gateway HTTP API

    HTTP APIs are up to 71% cheaper compared to REST APIs

    Latency

    Lambda isn't magically executing your code without server even tho they market it like it is. When an event occur, AWS will launch a docker container, wait for it to fully load all your dependencies and then run your handler.

    With a normal Node.js server on AWS EC2 or AWS ECS it's a one time cost because your server is always running and all your dependencies are already loaded but not on your lambda.

    As AWS says:

    This approach [Express.js and proxy all requests to your lambda] is generally unnecessary, and it’s often better to take advantage of the native routing functionality available in API Gateway. In many cases, there is no need for the web framework in the Lambda function, which increases the size of the deployment package. API Gateway is also capable of validating parameters, reducing the need for checking parameters with custom code. It can also provide protection against unauthorized access, and a range of other features more suited to be handled at the service level.

    Best practices for organizing larger serverless applications

    How to convert Express framework into plan Lambda

    To simplify your life, I would suggest you to use SAM CLI. It's very simple to get started with it.

    Install SAM CLI

    If you're following the MVC pattern for your Express app. You only need to take your service files where your core logic live.

    The folder structure I like to use for the lambda is the following

    Suppose it's a small calendar app

    ──src-ts
        ├───handlers
        │       getEvent.ts
        │
        ├───tests
        │   │   getEvent.tests.ts
        │   │
        │   └───utils
        │           utils.ts
        │
        └───utils
                utils.ts
                validation.ts
    

    It's important that your handler returns 3 thing

    1. Headers (JSON)
    2. statusCode (number)
    3. Body (stringified)

    You also need a template.yml file to describe the infrastructure that your lambda need

    AWSTemplateFormatVersion: 2010-09-09
    Description: Describe the lambda goal
    
    Transform:
        - AWS::Serverless-2016-10-31
    
    
    Resources:
        # API Gateway
        LambdaAPI:
            Type: AWS::Serverless::Api
            Properties:
                StageName: StageName
                Cors:
                    AllowMethods: "'POST, GET, OPTIONS'"
                    AllowHeaders: "'*'"
                    AllowOrigin: "'*'"
    
    
        # IAM Role
        LambdaRole:
            Type: AWS::IAM::Role
            Properties:
                AssumeRolePolicyDocument:
                    Version: 2012-10-17
                    Statement:
                        - Action:
                              - 'sts:AssumeRole'
                          Effect: Allow
                          Principal:
                              Service:
                                  - lambda.amazonaws.com
                ManagedPolicyArns:
                    - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
                    - arn:aws:iam::aws:policy/AmazonDynamoDBFullAccess
                    - arn:aws:iam::aws:policy/AmazonAPIGatewayInvokeFullAccess
    
        GetEvent:
            Type: AWS::Serverless::Function
            Properties:
                Runtime: nodejs12.x
                Timeout: 180
                FunctionName: youLambdaName
                Handler: src/handlers/getEvent.handler
                Role: !GetAtt LambdaRole.Arn
                Events:
                    Get:
                        Type: Api
                        Properties:
                            RestApiId: !Ref LambdaAPI
                            Path: /events/{eventid}
                            Method: GET
    
    

    Note I used typescript and but when compiled it's creating an src folder

    Some resource to help you more in depth:

    To Conclude

    The upside of using lambdas without Express are:

    • better scalability
    • cost optimization
    • lower latency
    • higher availability because you have multiple lambda for each business logic instead of one that run it all

    The downside of using lambdas without Express are:

    • You need to modify your existing code
    • Init time of your lambda needs to be part of your thinking when developing your logic
    • You need to learn SAM yaml template and read the AWS doc when you wanna add more functionality to your API infrastructure.

    Take advantage of the AWS infrastructure, don't try to go against it. All AWS Services are working together in a seamless and low latency way. You should remove Express from your infrastructure if you wanna go "Serverless".