Building a serverless application is exciting—until you hit a roadblock like the dreaded "Internal Server Error." Recently, I encountered this while creating a simple API with AWS Lambda and API Gateway to process query parameters and return a structured response. This post breaks down my debugging journey, what went wrong, and how I fixed it.
The Problem
My goal was straightforward:
- Accept
transactionId
,type
, andamount
as query parameters. - Log the parameters and return them in a JSON response.
Here’s the Lambda function I wrote initially:
import json
def lambda_handler(event, context):
transactionId = event['queryStringParameters']['transactionId']
transactionType = event['queryStringParameters']['type']
transactionAmount = event['queryStringParameters']['amount']
print(f"TransactionId = {transactionId}")
print(f"TransactionType = {transactionType}")
print(f"TransactionAmount = {transactionAmount}")
response = {
'transactionId': transactionId,
'type': transactionType,
'amount': transactionAmount,
'message': "Hello from lambda land"
}
return {
'statusCode': 200,
'headers': {
'Content-Type': 'application/json',
},
'body': json.dumps(response)
}
But when I tested the API through the browser, I kept getting:
- 500 Internal Server Error
- CORS errors when calling the API from my frontend.
What Went Wrong?
Missing Query Parameters:
If any query parameter was missing, the code would throw aKeyError
. This caused Lambda to fail and returned a generic error message without any details.No Error Handling:
There was no mechanism to catch and log errors. This made debugging harder because the API Gateway logs only showed "Internal Server Error."CORS Issues:
The response didn’t includeAccess-Control-Allow-Origin
, which is required to enable communication between the frontend and the API Gateway endpoint.
The Fix
I rewrote the Lambda function with robust error handling, validation, and proper CORS configuration.
import json
def lambda_handler(event, context):
try:
# Extract query string parameters
params = event.get('queryStringParameters', {})
transactionId = params.get('transactionId', 'N/A')
transactionType = params.get('type', 'N/A')
transactionAmount = params.get('amount', 'N/A')
# Validate parameters
if transactionId == 'N/A' or transactionType == 'N/A' or transactionAmount == 'N/A':
raise ValueError("Missing required query parameters")
# Log parameters
print(f"TransactionId = {transactionId}")
print(f"TransactionType = {transactionType}")
print(f"TransactionAmount = {transactionAmount}")
# Create response
transactionResponse = {
'transactionId': transactionId,
'type': transactionType,
'amount': transactionAmount,
'message': "Hello from lambda land"
}
return {
'statusCode': 200,
'headers': {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
},
'body': json.dumps(transactionResponse)
}
except Exception as e:
print(f"Error: {e}")
return {
'statusCode': 500,
'headers': {'Access-Control-Allow-Origin': '*'},
'body': json.dumps({'message': 'Internal Server Error', 'error': str(e)})
}
Key Changes and Why They Work
Safe Parameter Extraction:
Instead of directly accessingevent['queryStringParameters']
, I used.get()
with default values to preventKeyError
exceptions.Validation:
The code now checks if required parameters are missing and raises aValueError
. This ensures we only process valid inputs.Error Handling with Try-Except:
Wrapping the logic intry...except
allows us to catch errors and return a detailed response while logging the issue for debugging in CloudWatch.CORS Headers:
AddingAccess-Control-Allow-Origin
enables the frontend to communicate with the API successfully.
The Final Test
API Request
curl -X GET "https://<your-api-id>.execute-api.us-east-1.amazonaws.com/test/transactions?transactionId=123&type=credit&amount=1000"
Response
{
"transactionId": "123",
"type": "credit",
"amount": "1000",
"message": "Hello from lambda land"
}
Takeaways
- Validation is Crucial: Always validate inputs to ensure your application works as expected.
-
Error Handling Simplifies Debugging: Wrapping your logic in
try...except
helps identify issues quickly. - CORS Configuration Matters: If your API serves a frontend, always include CORS headers in the response.
-
Logs Are Your Friend: Use
print
statements to log details in CloudWatch and understand what’s happening.
What’s Next?
Now that I’ve successfully implemented and tested this API, I’m moving on to integrating it with my S3 bucket for file uploads. This will involve:
- Generating pre-signed URLs in Lambda.
- Updating the frontend to handle file uploads.
Have you encountered similar challenges while working with serverless applications? Let’s discuss in the comments!
Top comments (0)