Build a Serverless Quotes API
Wire up API Gateway and Lambda to serve a live REST API on AWS.
Introduction
30 Second Summary
Every time you check the weather, scroll a social media feed, or search for a restaurant, an API is delivering that data behind the scenes. APIs are the invisible backbone of the internet.
In this project, you will build a serverless Quotes API on AWS. You will create a public endpoint that returns random inspirational quotes as JSON, powered by API Gateway and a Lambda function.
What You'll Build
A REST API hosted on AWS that serves quotes on demand, using API Gateway to route requests and Lambda to process them.
By the end of this project, you'll have:
- A Python Lambda function that serves random quotes and handles path parameters.
- A REST API in API Gateway with two resources (/quotes and /quotes/{id}).
- A deployed API stage with a live URL you can test from your terminal using curl.
- Secret Mission: Add a POST endpoint that accepts new quotes and stores them in memory.
Will this cost me anything?
Nope. AWS Free Tier covers 1 million REST API calls per month for 12 months, and 1 million Lambda requests per month. This project stays well within those limits.
Set Up Your AWS Account
Every cloud project starts with access to the cloud itself. AWS provides the serverless infrastructure your Quotes API will run on.
In this step, you will create a free AWS account, orient yourself in the console, and confirm you can reach the Lambda service where your function will live.
In this step, get ready to:
- Create an AWS account (or sign in to an existing one).
- Set your region to us-east-1 (N. Virginia).
- Verify access to the Lambda console.
Create an AWS account
Already have an AWS account?
If you already have an AWS account, skip ahead to the next substep. Just make sure you can sign in to the AWS Management Console.
- Open https://aws.amazon.com/ in your browser.
- Click Create an AWS Account in the top-right corner.
- Enter your email address and choose an account name, then click Verify email address.
- Check your inbox for the verification code, enter it, and click Verify.
- Create a strong root user password and click Continue.
- Select Personal as your account type and fill in your contact information.
- Read and accept the AWS Customer Agreement, then click Continue.
- Enter a credit or debit card as your payment method and click Verify and Continue.
- Complete the phone verification step by entering the PIN sent to you.
- Select the Basic Support - Free plan.
- Click Complete sign up.
Will I be charged?
No. AWS requires a card for identity verification, but you will not be charged for this project. The AWS Free Tier includes 1 million Lambda requests and 1 million REST API calls per month for 12 months.
Account activation usually takes a few minutes. Check your email for the confirmation before continuing.
Sign in and set your region to us-east-1
AWS runs data centres in regions around the world. Setting a consistent region ensures all your resources live in the same place.
- Go to the AWS Management Console sign-in page and sign in with the email and password you just created.
- Look at the top-right corner of the console, next to your account name. You will see a region name displayed (for example, Ohio or Oregon).
- Click the region selector dropdown.
- Select US East (N. Virginia) us-east-1.
Why us-east-1?
This region is the default for new accounts and has the broadest service availability. Any region works for this project, but using us-east-1 keeps your setup consistent with the instructions.
Verify access to the Lambda console
- Navigate to the Lambda console by entering this URL in your browser:
- https://console.aws.amazon.com/lambda.
- Confirm you can see the Functions page. It will be empty since you have not created any functions yet.
You should see a Create function button on this page. This confirms your account is active and you have permission to use Lambda.
Can't see the Functions page?
If you see a permissions error, your account may still be activating. Wait a few minutes and try again. Activation can take up to 24 hours in rare cases.
Make sure your region is set to us-east-1 in the top-right corner. A different region could show a different console state.
help me access the Lambda console in AWS
Your AWS account is live and ready to go. Next up, you will create the Lambda function that serves your quotes.
Create the Lambda Function
Your API needs code that actually does something when a request arrives. In this project, that code lives in a Lambda function that picks a quote and sends it back as JSON.
In this step, you will create the Lambda function, write the Python handler that serves quotes, and run a test to confirm everything works before connecting it to API Gateway.
In this step, get ready to:
- Create a Lambda function in the AWS console.
- Write a Python handler that returns random quotes and supports fetching by ID.
- Test the function with a simulated API Gateway request.
Create the QuotesFunction in the Lambda console
- Return to the Lambda Functions page in the AWS Console (you should already be here from Step 1).
- Click Create function.
- Select Author from scratch.
- For Function name, enter QuotesFunction.
- For Runtime, select Python 3.13.
- Leave Architecture set to x86_64.
- Click Create function.
After a few seconds, the console takes you to your new function's page. You should see a green success banner at the top.
What just happened behind the scenes?
Lambda automatically created an IAM execution role for your function. This role gives QuotesFunction permission to write logs to CloudWatch. You did not need to configure this manually.
Don't see Python 3.13 in the list?
Make sure your region is set to us-east-1 (N. Virginia) in the top-right corner of the console. Some newer runtimes roll out to this region first.
Help me find Python 3.13 in the Lambda runtime dropdown.
Write the quotes API handler code
The function's code editor already has a placeholder. You will replace it with a handler that serves random quotes and supports looking up a specific quote by its ID.
- In the Code tab, click the lambda_function.py file in the built-in code editor.
- Select all the existing code and delete it.
- Paste the following code into the editor:
import json
import random
quotes = [
{"id": 1, "text": "The best way to predict the future is to invent it.", "author": "Alan Kay"},
{"id": 2, "text": "Stay hungry, stay foolish.", "author": "Steve Jobs"},
{"id": 3, "text": "Code is like humor. When you have to explain it, it is bad.", "author": "Cory House"},
{"id": 4, "text": "First, solve the problem. Then, write the code.", "author": "John Johnson"},
{"id": 5, "text": "Simplicity is the soul of efficiency.", "author": "Austin Freeman"},
]
def lambda_handler(event, context):
# Extract the HTTP method and path from the incoming event
http_method = event.get("httpMethod", "GET")
path = event.get("path", "")
path_parameters = event.get("pathParameters")
# Route the request based on method and path parameters
if http_method == "GET" and path_parameters and "id" in path_parameters:
return get_quote_by_id(path_parameters["id"])
elif http_method == "GET":
return get_random_quote()
else:
return build_response(405, {"error": "Method not allowed"})
def get_random_quote():
# Pick a random quote from the list
quote = random.choice(quotes)
return build_response(200, quote)
def get_quote_by_id(quote_id):
# Convert the ID from a string to an integer
try:
quote_id = int(quote_id)
except ValueError:
return build_response(400, {"error": "Invalid quote ID"})
# Search for the quote with the matching ID
for quote in quotes:
if quote["id"] == quote_id:
return build_response(200, quote)
return build_response(404, {"error": f"Quote with ID {quote_id} not found"})
def build_response(status_code, body):
# Format the response in the structure API Gateway expects
return {
"statusCode": status_code,
"headers": {
"Content-Type": "application/json"
},
"body": json.dumps(body)
}
What does this code do?
- quotes is a list of 5 dictionaries, each with an id, text, and author.
- lambda_handler is the entry point. It reads httpMethod and pathParameters from the event to decide which helper to call.
- get_random_quote picks a random quote using random.choice.
- get_quote_by_id searches the list for a quote matching the requested ID and returns a 404 if it is not found.
- build_response formats every response in the structure that API Gateway expects: a statusCode, headers, and a JSON-stringified body.
- Click Deploy in the DEPLOY section to save and deploy your code.
You should see a banner confirming the function was updated successfully.
Seeing a deployment error?
Check that you deleted ALL of the original placeholder code before pasting. Leftover lines from the default handler can cause syntax errors.
Also confirm the file is still named lambda_function.py in the editor. If the filename changed, the handler reference lambda_function.lambda_handler will not resolve.
Help me fix my Lambda deployment error.
✔️ Awesome, I've got everything!
Great. Double check you clicked Deploy and saw the success banner before continuing.
ⓧ I'd like to double check the full code
Here is the complete lambda_function.py for this step:
import json
import random
quotes = [
{"id": 1, "text": "The best way to predict the future is to invent it.", "author": "Alan Kay"},
{"id": 2, "text": "Stay hungry, stay foolish.", "author": "Steve Jobs"},
{"id": 3, "text": "Code is like humor. When you have to explain it, it is bad.", "author": "Cory House"},
{"id": 4, "text": "First, solve the problem. Then, write the code.", "author": "John Johnson"},
{"id": 5, "text": "Simplicity is the soul of efficiency.", "author": "Austin Freeman"},
]
def lambda_handler(event, context):
http_method = event.get("httpMethod", "GET")
path = event.get("path", "")
path_parameters = event.get("pathParameters")
if http_method == "GET" and path_parameters and "id" in path_parameters:
return get_quote_by_id(path_parameters["id"])
elif http_method == "GET":
return get_random_quote()
else:
return build_response(405, {"error": "Method not allowed"})
def get_random_quote():
quote = random.choice(quotes)
return build_response(200, quote)
def get_quote_by_id(quote_id):
try:
quote_id = int(quote_id)
except ValueError:
return build_response(400, {"error": "Invalid quote ID"})
for quote in quotes:
if quote["id"] == quote_id:
return build_response(200, quote)
return build_response(404, {"error": f"Quote with ID {quote_id} not found"})
def build_response(status_code, body):
return {
"statusCode": status_code,
"headers": {
"Content-Type": "application/json"
},
"body": json.dumps(body)
}
Test the function with a simulated API Gateway event
Before wiring this function up to a live API, you will send it a fake request that mimics what API Gateway will send later. This confirms your code runs without errors.
- In the TEST EVENTS section, choose Create test event.
- For Event name, enter TestRandomQuote.
- For Template, search for and select the API Gateway AWS Proxy template. This populates the JSON editor with a sample event matching the format API Gateway sends to Lambda.
- Click Save, then click Test.
Under Execution result, expand Details. You should see a response with statusCode 200 and a body containing a JSON quote.
What is the API Gateway AWS Proxy template?
This template provides a sample JSON event with fields like httpMethod, path, pathParameters, and headers. It simulates the exact structure that API Gateway will send to your function once they are connected.
Your handler reads these fields to decide what to do. Since the template defaults to a GET request with no path parameters, lambda_handler routes it to get_random_quote().
Test returning an error?
If you see a statusCode of 405, check that the template event has httpMethod set to GET. Some templates default to POST.
If you see a Python traceback in the logs, go back to the Code tab and check for typos. Make sure you clicked Deploy after pasting the code.
Help me debug my Lambda test error.
Your function is working. Next up, you will create a REST API in API Gateway and connect it to this Lambda function so the world can reach it over the internet.
Create the REST API in API Gateway
Your Lambda function can process requests and return quotes. But right now, nobody outside AWS can reach it. You need a public front door that accepts HTTP requests from the internet and forwards them to your function.
That front door is API Gateway. In this step, you will create a REST API and define the URL structure (called resources) that clients will use to request quotes.
In this step, get ready to:
- Create a REST API called QuotesAPI in API Gateway.
- Build a resource tree with /quotes and /quotes/{id} endpoints.
Create the QuotesAPI REST API
A REST API in API Gateway is the container that holds all your endpoints, methods, and deployment settings. You will create one named QuotesAPI with a Regional endpoint type, meaning it is optimized for clients in the same AWS region.
- Open the API Gateway console by navigating to https://console.aws.amazon.com/apigateway in your browser.
- Find the REST API card (not the one labeled "Private") and choose Build.
- For API name, enter QuotesAPI.
- Keep API endpoint type set to Regional.
- Keep IP address type as IPv4.
- Choose Create API.
What is a Regional endpoint?
A Regional endpoint means your API is deployed in a single AWS region and is optimized for clients calling from that same region. This is the default and simplest option.
The alternative (Edge-optimized) routes traffic through CloudFront for global distribution. For learning purposes, Regional keeps things straightforward.
You should now see the API Gateway editor with your new QuotesAPI. The Resources panel shows a single / (root) resource.
Don't see the REST API card?
Make sure you are looking at the main API Gateway landing page, not inside an existing API. If you see a list of APIs instead of the Build cards, choose Create API in the top-right corner to get back to the selection screen.
help me find the REST API Build option in API Gateway
Create the /quotes resource
Resources define the URL paths your API responds to. Think of them as folders in a URL. Your first resource will be /quotes, which is the endpoint clients hit to get quotes.
- In the Resources panel, make sure the / (root) resource is selected.
- Choose Create resource.
- Keep Proxy resource turned off.
- Keep Resource path as /.
- For Resource name, enter quotes.
- Keep CORS turned off.
- Choose Create resource.
You should now see /quotes appear as a child of the root resource in the Resources tree.
Create the /quotes/{id} child resource
Your API also needs to serve a specific quote by its ID. In REST API design, path parameters use curly braces to indicate a variable segment. The resource /quotes/{id} means "a single quote identified by the value after the slash."
- Select the /quotes resource in the Resources tree.
- Choose Create resource.
- For Resource name, enter {id}.
- Keep CORS turned off.
- Choose Create resource.
What is a path parameter?
The curly braces around {id} tell API Gateway that this part of the URL is a variable. When someone requests /quotes/3, API Gateway captures 3 as the value of id and passes it to your Lambda function.
This is how your get_quote_by_id function receives the quote ID from the request URL.
Your Resources tree should now show three entries: / (root), /quotes, and /quotes/{id}.
Don't see all three resources?
If /quotes/{id} is not nested under /quotes, you may have created it under the root / instead. Delete the misplaced resource, select /quotes first, then choose Create resource again.
help me fix my API Gateway resource tree structure
Your API structure is in place. Next up, you will connect these resources to your Lambda function so that requests actually get processed and return quotes.
Connect API Gateway to Lambda
Your API has a structure with resources that define URL paths. But those resources don't do anything yet.
In this step, you'll wire each resource to your Lambda function using Lambda proxy integration. This tells API Gateway to forward the entire HTTP request to your function and return whatever it sends back.
In this step, get ready to:
- Create a GET method on /quotes with Lambda proxy integration.
- Create a GET method on /quotes/{id} with Lambda proxy integration.
- Test the connection from the API Gateway console.
Create the GET method on /quotes
Each resource in your API needs at least one HTTP method attached to it before it can accept requests. You'll start by adding a GET method to the /quotes resource.
- In the Resources tree of your QuotesAPI, select the /quotes resource.
- Choose Create method.
- For Method type, select GET.
- For Integration type, select Lambda function.
- Turn on Lambda proxy integration.
- For Lambda function, enter QuotesFunction.
- Choose Create method.
What is Lambda proxy integration?
With proxy integration, API Gateway passes the entire incoming HTTP request (method, path, headers, body) directly to your Lambda function as the event object. Your function decides how to handle it and returns a response in a specific format (statusCode, headers, body).
This is the simplest integration type because you don't need to configure any request or response mapping templates in API Gateway. Your Lambda function handles all the routing logic itself.
Why did a permission popup appear?
When you choose Create method, API Gateway may show a message about adding permission to invoke your Lambda function. This is normal. API Gateway automatically grants itself permission to call QuotesFunction so you don't need to configure any IAM policies manually.
You should now see the GET method listed under /quotes in the Resources tree.
Don't see the GET method under /quotes?
Make sure you selected the /quotes resource (not the root /) before choosing Create method. If you accidentally created the method on the wrong resource, select the method and delete it, then try again on /quotes.
Help me fix my GET method on /quotes
Create the GET method on /quotes/{id}
Now you'll do the same thing for the /quotes/{id} resource so your API can serve individual quotes by their ID.
- In the Resources tree, select the /quotes/{id} resource.
- Choose Create method.
- For Method type, select GET.
- For Integration type, select Lambda function.
- Turn on Lambda proxy integration.
- For Lambda function, enter QuotesFunction.
- Choose Create method.
You should now see GET methods listed under both /quotes and /quotes/{id} in the Resources tree.
Seeing an error about the Lambda function?
Double-check that you entered QuotesFunction exactly as written (case-sensitive). Also confirm that your Lambda function is in the same region (us-east-1) as your API Gateway.
Help me connect my Lambda function to API Gateway
Test the API from the console
API Gateway has a built-in test feature that lets you invoke your method without deploying first. This is useful for catching issues early.
- In the Resources tree, select the GET method under /quotes.
- Choose the Test tab. You might need to choose the right arrow button to show the tab.
- Choose Test.
What should I see?
The test results panel shows several fields. Look for Status showing 200 and a Response body containing a JSON object with id, text, and author fields. The quote will be random each time you run the test.
Seeing a 500 error or no response?
A 500 status usually means your Lambda function has a code error. Switch back to the Lambda console, check that you deployed the latest code, and verify there are no syntax errors in lambda_function.py.
Also confirm that Lambda proxy integration is turned on. Without it, API Gateway won't pass the request in the format your function expects.
Help me debug my API Gateway test failure
Your API Gateway is now fully connected to your Lambda function. Next up, you'll deploy the API to a live stage and test it from your terminal with curl.
Deploy and Test Your Live API
Your API is fully wired up and tested inside the console. Now it's time to make it available to the outside world.
Deploying an API in API Gateway means creating a "stage" that gives your API a public URL anyone can reach. You'll deploy to a dev stage and test it live from your Mac terminal.
In this step, get ready to:
- Deploy your REST API to a live stage.
- Test all three endpoints from Terminal using curl.
Deploy the API to a dev stage
A stage is a named snapshot of your API that gets its own public URL. Think of it like publishing a version of your API that anyone on the internet can call.
- In the API Gateway console, choose Deploy API.
- For Stage, select New stage.
- For Stage name, enter dev.
- Choose Deploy.
What is a stage?
A stage represents a lifecycle phase of your API (like dev, staging, or prod). Each stage gets its own unique URL, so you can test changes on dev without affecting production users.
- Under Stage details, find the Invoke URL and copy it.
What does the invoke URL look like?
Your invoke URL follows this format: https://{api-id}.execute-api.us-east-1.amazonaws.com/dev. The {api-id} part is unique to your API. You'll append resource paths like /quotes to this base URL when testing.
Test the random quote endpoint from Terminal
Your API is live. Anyone with the URL can call it. Let's prove it works by calling it from outside the AWS console using curl in Terminal.
- Press Cmd+Space to open your search bar.
- Type Terminal and press Enter to open it.
- Test the random quote endpoint by running this command:
curl [[INVOKE_URL="https://abc123.execute-api.us-east-1.amazonaws.com/dev"]]/quotes
You should see a JSON response containing one of the five quotes, like this:
{"id": 2, "text": "Stay hungry, stay foolish.", "author": "Steve Jobs"}
Why a random quote each time?
Your Lambda function uses random.choice() to pick from the quotes list. Run the command a few times and you'll see different quotes come back.
Seeing an error instead of a quote?
Make sure you pasted the full invoke URL including https:// at the start and /dev before /quotes. The full path should end with /dev/quotes.
If you see "Missing Authentication Token", you may be hitting the root path /dev without /quotes appended. Double check the URL ends with /dev/quotes.
Help me debug my curl command for the Quotes API.
Test specific and invalid quote endpoints
Your API also handles path parameters. Let's test fetching a specific quote by ID, and confirm the error handling works for IDs that don't exist.
- Test fetching the quote with ID 1 by running this command:
curl [[INVOKE_URL="https://abc123.execute-api.us-east-1.amazonaws.com/dev"]]/quotes/1
You should see the Alan Kay quote returned:
{"id": 1, "text": "The best way to predict the future is to invent it.", "author": "Alan Kay"}
- Now test an invalid ID by running this command:
curl [[INVOKE_URL="https://abc123.execute-api.us-east-1.amazonaws.com/dev"]]/quotes/99
You should see a 404 error response:
{"error": "Quote with ID 99 not found"}
What just happened?
When you hit /quotes/1, API Gateway captured 1 as a path parameter and passed it to your Lambda function inside event["pathParameters"]["id"]. Your function looked up that ID in the quotes list and returned it.
For ID 99, the function searched the list, found no match, and returned a 404. This is the get_quote_by_id function handling the request end-to-end.
Getting "Internal Server Error" on /quotes/1?
This usually means your Lambda function has a code error. Return to the Lambda console, check the code in lambda_function.py matches the code from Step 2, and make sure you chose Deploy after your last edit.
Also confirm the /quotes/{id} resource in API Gateway has Lambda proxy integration turned on. Without proxy integration, the path parameters won't reach your function correctly.
Help me fix my API Gateway path parameter issue.
Secret mission
Add a POST Endpoint to Submit Quotes
Your API can serve quotes, but what if users want to contribute their own? In this secret mission, you will add a POST endpoint that accepts new quotes via JSON and stores them in memory. This means modifying both your API Gateway configuration and your Lambda function to handle a completely different HTTP verb.
Clean Up Your Resources
Clean Up Your Resources
Decide whether to keep your resources running, pause them to come back later, or delete them entirely.
All resources in this project are covered by the AWS Free Tier. You will not be charged unless you exceed 1 million API calls per month (API Gateway) or 1 million Lambda requests per month.
Resources you used:
- Lambda function (QuotesFunction).
- IAM execution role (auto-created with QuotesFunction).
- REST API in API Gateway (QuotesAPI).
Keep everything running
No action needed. Choose this if you are still actively building or experimenting with your API.
- Your API stays live and accessible at its invoke URL.
- Lambda and API Gateway are serverless. They only charge per request, so an idle API costs nothing.
- The free tier covers 1 million requests per month for both services during your first 12 months.
Pause - I'll come back to this later
Shut down external access while keeping your resources intact so you can resume later.
- Because Lambda and API Gateway are serverless, they do not run continuously. An idle API receives no requests and incurs no cost.
- If you want to prevent anyone from calling your endpoint while you are away, you can delete the dev stage in API Gateway. This removes the public URL without deleting the API definition itself.
- When you are ready to resume, redeploy to a new stage to get a fresh invoke URL.
Delete - I don't want to use this again
Remove all project resources completely. Follow each step below in order.
Delete the REST API:
- Open the API Gateway console at https://console.aws.amazon.com/apigateway.
- On the APIs page, select QuotesAPI.
- Choose API actions, then choose Delete API.
- In the Delete API dialog box, enter confirm, and then choose Delete.
Delete the Lambda function:
- Open the Lambda console at https://console.aws.amazon.com/lambda.
- On the Functions page, select QuotesFunction.
- Choose Actions, then choose Delete.
- Type confirm in the text input field and choose Delete.
Delete the execution role:
- Open the Roles page of the IAM console at https://console.aws.amazon.com/iam.
- In the search box, enter QuotesFunction to find the auto-created role.
- Select the role, then choose Delete.
- Enter the role name in the confirmation field and choose Delete.
That's a Wrap!
That's a Wrap!
Nice work! You just built a serverless REST API on AWS from scratch.
You've learned how to:
- Built a Python Lambda function that serves random quotes and handles path parameters.
- Created a REST API in API Gateway with multiple resources and Lambda proxy integration connecting your API to serverless compute.
- Deployed the API to a named stage and tested it live from the terminal with curl.
- Secret Mission: Added a POST endpoint to accept new quotes, handling different HTTP methods in a single Lambda function.
Ready to quiz yourself?