How to Build a Serverless App using the AWS Console

The console test passes and the live request still comes back as an internal server error. The JSON you typed in the Test tab is not the request object an HTTP trigger sends, so the handler that worked in the console has nothing to read.

I ran both payloads through one handler on this machine. The console event came back 400, and the same handler returned the greeting for the trigger-shaped event.

What the Lambda console actually builds for you

A Lambda function is a handler plus the configuration around it, and the console creates both in one pass. Knowing which piece owns what keeps you from editing the wrong pane when something misbehaves.

PieceWhat it isWhere you change it
FunctionThe named deployment that receives eventsFunctions page
HandlerThe exported method Lambda calls, named file.exportCode tab
Execution roleThe IAM role granting CloudWatch Logs writesConfiguration, Permissions
$LATESTThe mutable version every deploy overwritesCode tab, Deploy
EndpointA function URL or API route that invokes the functionConfiguration, Function URL

The console creates the execution role and the log group along with the function, so a first build fits in one browser tab. For the packaging in version control, the Serverless Framework walkthrough moves the same function into a YAML service.

What you need before you press Create function

The runtime decision locks more than the syntax, and the region decides where both the endpoint host and the log group live.

  • An AWS account with permission to create IAM roles, since the console attaches one to the function.
  • The Node.js 24 runtime, which is generally available for Lambda and runs the handler in this walkthrough.
  • A browser session in the region you want the endpoint in, so the function URL and its logs stay together.
  • The Console access role for the account, because the generated role writes the invocation logs you will read later.

Blueprints ship a starter project plus a policy template, and the sample handler they install reads event keys that an HTTP request never sends. The blueprint tile is still on the Create function page, and it is not the tile to pick here.

Create the function and give it an HTTP endpoint

The build runs in five moves. Only the last one gives the function an address that a client can call.

Create the function

Open the Functions page of the Lambda console, choose Create function, and pick Author from scratch. That option keeps the code editor and the handler name under your control, which the blueprint tile does not.

  1. Enter a function name, for example my-serverless-app.
  2. For Runtime, choose Node.js 24.
  3. Leave the architecture as the default x86_64.
  4. Choose Create function. Lambda writes a starter file and creates an execution role with CloudWatch Logs permissions.

The console names the starter file index.mjs with an exported handler, and the handler name is the file name plus the export name, so index.mjs exporting handler appears in the configuration as index.handler.

Write the handler

Replace the starter code with a handler that reads the request the trigger sends and returns the response contract the trigger expects, because the event carries the HTTP method under requestContext.http, the path under rawPath, and the query values under queryStringParameters.

export const handler = async (event, context) => {
  const http = event?.requestContext?.http;

  if (!http) {
    console.log('No requestContext.http on the event. Keys received:',
      Object.keys(event ?? {}).join(', '));
    return {
      statusCode: 400,
      headers: { 'content-type': 'application/json' },
      body: JSON.stringify({
        error: 'This function expects an HTTP event in payload format 2.0.',
        receivedKeys: Object.keys(event ?? {}),
      }),
    };
  }

  const name = event.queryStringParameters?.name ?? 'world';
  console.log(`${http.method} ${event.rawPath} handled by ${context.functionName}`);

  return {
    statusCode: 200,
    headers: { 'content-type': 'application/json' },
    body: JSON.stringify({
      greeting: `Hello, ${name}!`,
      method: http.method,
      path: event.rawPath,
      requestId: event.requestContext.requestId,
      logGroup: context.logGroupName,
    }),
  };
};

The guard on requestContext.http is the piece I wrote first, because a wrong event shape should fail loudly instead of returning undefined values. Beside it sits the returned object, which uses the three fields the payload format 2.0 contract reads.

The request shape I checked in the API Gateway documentation is the test event here, with the query string replaced by a name.

Add a test event that matches the trigger

A hello-world template with three string keys is what the Test tab offers first, and a handler like the one above cannot read it. The console run then fails while the deployed endpoint works, which is the reverse of the failure people brace for.

Terminal output showing the handler returning statusCode 400 for the console test event
The console test event reaches the handler without a request context, so the handler answers 400.

Create the test event from the shape the trigger sends instead. In the Test tab, create a new event, paste the body below, and save it as a reusable event so the next session starts from it.

{
  "version": "2.0",
  "routeKey": "$default",
  "rawPath": "/",
  "rawQueryString": "name=Ninad",
  "headers": { "host": "abcdefg.lambda-url.us-east-1.on.aws" },
  "queryStringParameters": { "name": "Ninad" },
  "requestContext": {
    "http": { "method": "GET", "path": "/", "protocol": "HTTP/1.1" },
    "requestId": "e5f6a1b2-3c4d-5e6f-7a8b-9c0d1e2f3a4b",
    "timeEpoch": 1789663200000
  },
  "body": null,
  "isBase64Encoded": false
}

A shareable test event is worth the extra click, and the Stack Overflow question behind this section is the reason. An event saved in one browser profile is a private assumption about the payload, so the teammate who deploys the next change starts from the wrong shape.

Add the function URL

A function URL is the shortest console route to a public HTTPS address, it needs no second service, and the endpoint it generates never changes after creation. It attaches to an alias or to $LATEST.

  1. Open the Configuration tab and choose Function URL.
  2. Choose Create function URL.
  3. Set Auth type to NONE for a public endpoint, or AWS_IAM when every caller signs requests with SigV4.
  4. Save, then copy the https address that ends in on.aws.

The auth choice decides who can call the endpoint, not what the handler receives. With NONE, the browser and curl both work and the event still arrives in payload format 2.0.

Call the endpoint with curl

The endpoint call is the first test in this build that uses the trigger’s event shape, and the query string is where the handler reads the name.

curl -s 'https://abcdefg.lambda-url.us-east-1.on.aws/?name=Ninad'

The response body is the JSON the handler returned, so the fields line up against the values you passed in. That agreement is the only check in the build that covers the trigger.

Terminal output showing the handler returning statusCode 200 for a payload format 2.0 request event
The same handler returns 200 once the event carries requestContext and the query fields.

When the console test passes and the endpoint still fails

I ran the same handler twice, once with each event, and then I compared what each run received. The console event carries three string keys, while the trigger request carries requestContext, the headers map, and the query values.

The symptom in a deployed function is a 502 from the endpoint while the Test tab reports success. The two come from one split, because the console test replays the object you saved and a trigger builds its own.

Check the invocation log before changing code, because the Test tab links the log stream for the run you just made. The Configuration tab holds the log group name, which is /aws/lambda/ followed by the function name. A handler error appears there with the stack trace and the event that caused it.

Align the event rather than the handler, which means copying the request body a live trigger sends into a new shareable test event. Keep the guard that answers 400 for anything else, because a handler that fails loudly on the wrong shape saves the afternoon you would spend reading undefined values.

What you seeWhat it meansFirst check
The endpoint answers 502The handler threw before it returned a responseCloudWatch log stream for that invocation
The handler reads undefinedThe event lacks the field the code expectsCompare the saved test event with a captured trigger request
The console test answers 400The saved event is not a payload format 2.0 requestPaste a trigger body into a shareable test event

A limit is worth knowing before you build a habit around the test event. No AWS CLI or SDK call creates or updates it, so it stays a console artifact. SAM, CDK, and Terraform manage functions and aliases, not test events.

Publish a version and move an alias without changing the URL

Every deploy overwrites $LATEST, and the code you tested stays the running code only until someone deploys again. Publishing a version locks the code, the runtime, and the memory setting, which is what turns a save into a release.

An alias is a named pointer to one of the published versions, then the endpoint keeps its address while the alias moves. Clients never learn a new host.

aws lambda publish-version --function-name my-serverless-app
aws lambda create-alias --function-name my-serverless-app \
  --name prod --function-version 1
aws lambda update-alias --function-name my-serverless-app \
  --name prod --function-version 2

Point the function URL at the alias rather than at $LATEST and the endpoint serves whatever the alias points to. That indirection is not optional, because a function URL cannot target a numbered version.

What the endpoint tells you that the console test cannot

The endpoint is the only test that includes the shape your callers send. Keep the console test event an exact copy of one live request, and a passing test and a passing endpoint mean the same thing.

After every deploy, run the curl call once against the alias URL. A greeting that comes back with the name you passed means the request path, the handler, and the version agree.

CheckWhat passing proves
Console test with the copied eventThe handler reads the request fields and its guard stays quiet
curl against the endpointThe trigger, the request path, and the running version agree
Publish a version, then curl the alias URLCallers reach the release, not the editor

FAQ

The Lambda console raises a few questions once the first function runs, and these are worth answering before a client points at it.

What is AWS Lambda used for?

Lambda runs a function for a request or an event without a server you manage. It suits small HTTP endpoints, scheduled jobs, and file or queue processing, because you pay for the invocation time rather than an idle host.

Does a function URL replace API Gateway?

Not for every case. A function URL gives one function a public HTTPS address with an auth type and CORS settings, and it cannot attach to a numbered version. API Gateway adds routing, request validation, usage plans, and a custom domain in front of several functions.

How do I check the logs for a failed invocation?

Open the Monitor tab of the function and choose View CloudWatch logs, or read the log group named /aws/lambda/ plus the function name. Each stream lists the report line with the duration, the billed duration, and any stack trace.

How do I keep the console test event in step with the trigger?

Save a shareable test event built from a real request body, and update it whenever the client changes the fields it sends. Shareable events travel with the function for other console users, while private events stay in your browser.

Pankaj Kumar
Pankaj Kumar

Pankaj Kumar is the founder and CEO of CodeForGeek, with more than 14 years in IT. He is an open-source enthusiast who enjoys sharing what he learns through CodeForGeek and YouTube, with a focus on Python, data analytics, machine learning, Angular, Node.js, and Kafka.

Articles: 335