Path Parameters and Query Strings
Making your API dynamic by reading input from the URL.
What you'll learn
- Extracting path parameters from the event
- Reading query string parameters
- Creating multiple binaries in one project
A real API needs to handle input. Whether it's a user ID in the URL (/users/123) or a search term in a query string (/search?q=rust), your Lambda needs to know how to read these values.
Adding a dynamic handler
Instead of stuffing everything into hello.rs, we're going to create a new file specifically for a "Hello Name" feature. This demonstrates how you can have multiple Lambda functions in a single Rust project.
Create a new file at src/bin/hello_name.rs:
use aws_lambda_events::apigw::{ApiGatewayProxyRequest, ApiGatewayProxyResponse};
use aws_lambda_events::encodings::Body;
use aws_lambda_events::http::HeaderMap;
use lambda_runtime::{run, service_fn, Error, LambdaEvent};
async fn handler(event: LambdaEvent<ApiGatewayProxyRequest>) -> Result<ApiGatewayProxyResponse, Error> {
// 1. Extract path parameters
let path_params = &event.payload.path_parameters;
let name = path_params.get("name").unwrap_or(&"Guest".to_string());
// 2. Extract query parameters (optional)
let query_params = &event.payload.query_string_parameters;
let title = query_params.get("title").map(|v| format!("{} ", v)).unwrap_or_default();
let message = format!("Hello, {}{}!", title, name);
let body = serde_json::json!({
"statusCode": 200,
"message": message,
"data": null
});
let mut headers = HeaderMap::new();
headers.insert("Content-Type", "application/json".parse().unwrap());
let mut response = ApiGatewayProxyResponse::default();
response.status_code = 200;
response.headers = headers;
response.body = Some(Body::Text(serde_json::to_string(&body)?));
Ok(response)
}
#[tokio::main]
async fn main() -> Result<(), Error> {
run(service_fn(handler)).await
}Updating Cargo.toml
We need to tell Rust about this new binary. Update your Cargo.toml:
[[bin]]
name = "hello"
path = "src/bin/hello.rs"
[[bin]]
name = "hello_name"
path = "src/bin/hello_name.rs"Updating template.yaml
Now we need to tell AWS to create a second Lambda function and connect it to a new route.
# ... (Globals and API definition) ...
Resources:
# ... (HelloFunction and GetHelloMethod) ...
# The New Route: /hello/{name}
HelloNameResource:
Type: AWS::ApiGateway::Resource
Properties:
ParentId: !Ref HelloResource
PathPart: '{name}'
RestApiId: !Ref RustExample
# The New Lambda
HelloNameFunction:
Type: AWS::Serverless::Function
Metadata:
BuildMethod: rust-cargolambda
Properties:
CodeUri: .
Handler: bootstrap
Runtime: provided.al2023
Architectures: ["arm64"]
# The New Method
GetHelloNameMethod:
Type: AWS::ApiGateway::Method
Properties:
RestApiId: !Ref RustExample
ResourceId: !Ref HelloNameResource
AuthorizationType: NONE
HttpMethod: GET
Integration:
Type: AWS_PROXY
IntegrationHttpMethod: POST
Uri: !Sub "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HelloNameFunction.Arn}/invocations"
# Permission for the new function
HelloNameFunctionApiPermission:
Type: AWS::Lambda::Permission
Properties:
FunctionName: !Ref HelloNameFunction
Action: lambda:InvokeFunction
Principal: apigateway.amazonaws.com
SourceArn: !Sub "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${RustExample}/*"Info
Notice that PathPart is {name}. The curly braces tell API Gateway that this is a dynamic variable. This value is what shows up in the path_parameters map in our Rust code.
Deploying again
Since we added a new binary and resources, we need to build and deploy:
sam build
sam deployNow you can test it:
curl https://your-api.com/dev/hello/Hazeezet
Response: {"message": "Hello, Hazeezet!"}
curl https://your-api.com/dev/hello/Hazeezet?title=Dev
Response: {"message": "Hello, Dev Hazeezet!"}
This is working great, but our response structure is a bit messy and repetitive. In the next post, we'll create a standardized JSON response structure to make our API professional.