Fading Coder

One Final Commit for the Last Sprint

Home > Tools > Content

Building a Serverless REST API with AWS Lambda and API Gateway

Tools 1

For developers, the core value lies in creating business logic and implementing functional requirements. However, we often spend valuable time on tedious server operations: configuration, deployment, patching, monitoring, scaling... Just to run a simple backend API, we may face a complex server management process. Have you ever thought, when receiving server downtime alerts late at night or scrambling to scale due to sudden traffic spikes, if there is a way to focus only on code and leave everything else to professionals?

The answer is yes. This is the revolution brought by the "Serverless" architecture. It doesn't mean there are no servers, but rather that server management is completely removed from the developer's view. In the AWS ecosystem, the core practitioners of this concept are AWS Lambda and AWS API Gateway.

Think of AWS Lambda as a "code executor"; you just upload your business logic code (e.g., a Python function), and it runs on demand, automatically scaling based on request volume without reserving any computing resources. API Gateway acts as the "front door," responsible for receiving HTTP requests from the internet and securely and reliably forwarding them to the backend AWS Lambda function for processing.

The charm of this combination lies in its complete transformation of the development and deployment paradigm. Developers' work is simplified to the purest part: writing code that delivers business value. Infrastructure availability, scalability, and security are all guaranteed by AWS at the underlying level. This is more than just "no servers"; it's a fundamental shift in development philosophy—from "managing infrastructure" to "implementing bussiness logic." This shift can greatly shorten development cycles, reduce operational costs, and allow us to invest more time in functional innovation that truly creates value for users.

Next, let's build a highly available, auto-scaling REST API from scratch using AWS Lambda and AWS API Gateway.

Preparation: The AWS "Toolbox" 🧰

Before starting, ensure you have the following tools and environment ready:

  • A valid AWS account: If you don't have one, you can easily register. All services and usage involved in this guide are within the AWS service coverage, meaning you can quickly complete the entire experiment.
  • Python 3 basics: We will use Python as the programming language for the AWS Lambda function; you need to understand basic Python syntax.
  • AWS CLI: Although we will mainly operate through the AWS Management Console in this guide, installing and configuring the command-line tool (CLI) is recommended in professional development practices to help you achieve deployment automation in the future.

Step 1: Create a Lambda Function - The "Brain" of the API 🧠

The AWS Lambda function is the core processing unit of our API, containing all business logic. Let's create it first.

A. Create an Execution Role (IAM Role)

First, we need to create an "identity" for the AWS Lambda function and grant it necessary "permissions." In AWS, this is achieved through IAM (Identity and Access Management) roles. This is like issuing a work permit for your function, specifying what it is allowed to do.

Why is this step needed? This is the core of AWS security best practices—the principle of least privilege. Our function at least needs permission to write runtime logs to AWS CloudWatch Logs so we can easily debug when issues arise.

  1. Log in to the AWS Management Console and navigate to the IAM service.
  2. In the left navigation bar, select Roles, then click Create role.
  3. On the "Select trusted entity" page, choose AWS service, then select Lambda under "Use case." Click Next.
  4. On the "Add permissions" page, search for and check the policy named AWSLambdaBasicExecutionRole. This policy includes basic permissions to write logs to CloudWatch Logs. Click Next.
  5. Name your role, e.g., my-lambda-api-role, and add a simple description. After confirming, click Create role.

Now, our AWS Lambda function has an "identity card" with basic work permissions.

B. Create the AWS Lambda Function

Next, we officially create the AWS Lambda function itself.

  1. In the AWS Management Console, navigate to the Lambda service.
  2. Click Create function.
  3. Select Author from scratch.
  4. In the "Basic information" section, configure as follows:
    1. Function name: Enter a descriptive name, such as apiHandler.
    2. Runtime: Choose a newer Python version, e.g., Python 3.11 or higher.
    3. Architecture: Keep the default x86_64.
    4. Permissions: Expand "Change default execution role," select Use an existing role, then find and select the role we just created, my-lambda-api-role, from the dropdown list.
  5. Click Create function.

Wait a moment, and your AWS Lambda function will be created. You will be taken to the function's configuration page.

C. Write Handler Code

Now, let's inject real logic into this "brain." In the "Code source" editor on the function configuration page, you will see a default lambda_function.py file.

Replace its content with the following code:

import json

def lambda_handler(event, context):
    """
    This function handles requests from API Gateway.
    It extracts the 'name' from query string parameters and returns a personalized greeting.
    """
    print("Received event object:", event)  # Print event object for debugging

    # Parse 'name' from query string parameters, default to 'World' if not present
    # API Gateway proxy integration places query parameters in the 'queryStringParameters' field
    name = "World"
    if event.get("queryStringParameters") and event["queryStringParameters"].get("name"):
        name = event["queryStringParameters"]["name"]

    # Build response body to return to API Gateway
    response_body = {
        "message": f"Hello, {name}!",
        "input_event": event  # Include the received event for structural understanding
    }

    # This is the standard return format required by API Gateway proxy integration
    # Must include statusCode, headers, and body
    response = {
        "statusCode": 200,
        "headers": {
            "Content-Type": "application/json",
            "Access-Control-Allow-Origin": "*"  # Allow cross-origin access
        },
        "body": json.dumps(response_body)
    }

    return response

After writing the code, click the Deploy button in the top right to save and deploy your changes.

Let's interpret the key parts of this code:

  • lambda_handler(event, context): This is the entry point of the Lambda function. AWS Lambda calls this function to process requests.
    • event: This is a dictionary object containing all event information that triggers the function. When triggered by AWS API Gateway, it encapsulates complete HTTP request information, including path, headers, body, and query string parameters like queryStringParameters.
    • context: This object provides runtime information about the invocation, function, and execution environment, such as function name, memory limit, etc. We don't use it in this example.
  • Response format: This is the most important point to note in this section. When using the "Lambda proxy integration" mode of AWS API Gateway, the return value of the AWS Lambda function must follow a specific JSON structure. It must include statusCode (HTTP status code), headers (HTTP response headers), and a body (response content as a string). Forgetting this format is one of the most common mistakes for beginners. We use json.dumps() to serialize the Python dictionary into a JSON string to meet the body requirement.

The logic of this code is very simple, but it perfectly demonstrates the operation mode of AWS Lambda as an API backend. More importantly, by understanding the structure of the event object, you master the core pattern of AWS Lambda integration with other AWS services. Whether it's an S3 object upload event or an SNS message notification, they will be packaged into a similar event dictionary and passed to the AWS Lambda function. Therefore, the skill you learn today has value far beyond building a simple API.

Step 2: Configure AWS API Gateway - The "Door" to the World 🚪

Our AWS Lambda function is ready, but it is currently just an isolated code snippet. We need to create a publicly accessible HTTP endpoint for it through AWS API Gateway.

A. Create a REST API

  1. In the AWS Management Console, navigate to the API Gateway service.
  2. On the main panel, find the REST API card (not REST API Private), and click Build.
  3. On the "Create new API" page, keep the default REST protocol and New API option.
  4. In "Settings," name your API, e.g., greetingApi, and optionally add a description.
  5. Keep "Endpoint type" as Regional.
  6. Click Create API.

B. Define Resources and Methods

In the RESTful world, an API consists of "resources" and "methods" that operate on resources. For example, /users is a resource, and GET /users is the method to retrieve a list of users.

Let's create a resource and method for our API.

  1. On the API's "Resources" page, ensure the root resource / is selected.
  2. Click the Actions dropdown menu, select Create resource.
  3. In "Resource name," enter greet. "Resource path" will automatically fill as /greet.
  4. Click Create resource.
  5. Now, the newly created /greet resource is selected. Click the Actions dropdown menu again, select Create method.
  6. In the small dropdown box that appears under /greet, select GET, then click the checkmark next to it to confirm.

C. Set Up Lambda Proxy Integration

This is the key step to connect AWS API Gateway and the AWS Lambda function.

  1. On the GET method setup page, configure as follows:
    1. Integration type: Select Lambda function.
    2. Check the Use Lambda proxy integration checkbox.
    3. Lambda region: Keep the current region.
    4. Lambda function: Start typing the name of the AWS Lambda function you created earlier, apiHandler; the console will automatically prompt and let you select.
    5. Keep Use default timeout checked.
  2. Click Save.
  3. The console will pop up a prompt, requesting "Grant API Gateway permission to invoke the Lambda function." This will automatically add the necessary permistions to the Lambda function's resource policy. Click OK.

Why emphasize Lambda proxy integration? Because this is the simplified mode recommended by AWS. In this mode, AWS API Gateway does not interfere with the content of requests and responses; instead, it packages the original HTTP request completely into an event object and passes it to AWS Lambda, and directly parses the JSON object returned by AWS Lambda that conforms to the format into an HTTP response. This greatly simplifies development; you don't need to configure complex request/response mapping templates in AWS API Gateway. All logic is concentrated in the AWS Lambda code, making maintenance and debugging easier.

Step 3: Deploy and Test - The Moment of Magic ✨

So far, everything we've done is just a blueprint. To make the API truly effective, we need to "deploy" it to a "stage." A stage can be understood as a snapshot or version of the API, typically used to distinguish different environments such as development (dev), testing (test), production (prod), etc.

  1. On the API's "Resources" page, click the Actions dropdown menu, select Deploy API.
  2. In the pop-up dialog, under "Deployment stage," select [New stage].
  3. Name the stage, e.g., test.
  4. Click Deploy.

After successful deployment, you will be taken to the "Stage Editor" page. At the top of the page, you will see a blue Invoke URL. This is the globally accessible API endpoint you just created!

Its format is similar to: https://{api-id}.execute-api.{region}.amazonaws.com/test

Now, let's test it. Open your terminal or use any API testing tool (such as Postman) to send a GET request to your API.

Test 1: Without parameters

Execute the following command in the terminal (replace the URL with your own invoke URL):

curl "https://{api-id}.execute-api.{region}.amazonaws.com/test/greet"

You should receive a response like:

{
  "message": "Hello, World!",
  "input_event": {...}
}

Test 2: With the name parameter

curl "https://{api-id}.execute-api.{region}.amazonaws.com/test/greet?name=Developer"

This time, the response will be more personalized:

{
  "message": "Hello, Developer!",
  "input_event": {...}
}

If you open this URL in a browser, you will see the same result.

Summary 🔭

Done! In not much time, a fully functional REST API has been born. To summarize briefly:

  • Fully managed: You haven't configured any servers.
  • Auto-scaling: This API can handle one request per second or smoothly scale to tens of thousands of requests per second without any intervention from you.
  • Pay-per-use: If there are no requests, you don't pay anything.
  • High availability: AWS Lambda and AWS API Gateway are themselves deployed across multiple availability zones, giving your API inherent high availability.

All of this can start at zero cost. Thanks to the AWS Free Tier, you get 1 million free AWS Lambda function invocations and 1 million AWS API Gateway calls per month. For personal projects, startups, or learning purposes, this almost means you can run such an API permanently for free.

You have taken the first step in serverless development. Next, you can explore more possibilities: integrate this API with AWS DynamoDB (a NoSQL database) to persist data, or use AWS Cognito to add user authentication and authorization to your API. The serverless world is full of infinite possibilities, waiting for you to create.

Finally, a reminder: if you no longer use the related services, remember to close them in the console to avoid exceeding the free tier and incurring charges.

Related Articles

Efficient Usage of HTTP Client in IntelliJ IDEA

IntelliJ IDEA incorporates a versatile HTTP client tool, enabling developres to interact with RESTful services and APIs effectively with in the editor. This functionality streamlines workflows, replac...

Installing CocoaPods on macOS Catalina (10.15) Using a User-Managed Ruby

System Ruby on macOS 10.15 frequently fails to build native gems required by CocoaPods (for example, ffi), leading to errors like: ERROR: Failed to build gem native extension checking for ffi.h... no...

Resolve PhpStorm "Interpreter is not specified or invalid" on WAMP (Windows)

Symptom PhpStorm displays: "Interpreter is not specified or invalid. Press ‘Fix’ to edit your project configuration." This occurs when the IDE cannot locate a valid PHP CLI executable or when the debu...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.