TutorialbeginnerPart 15 of 18

Environment Variables and Stages

Controlling your code's behavior based on the environment.

April 26, 20262 min read

What you'll learn

  • Using Parameters in template.yaml
  • Passing Environment Variables to Lambda
  • Reading environment variables in Rust
Prerequisites:Previous post completed (One Lambda, Multiple Endpoints)

You should never hardcode values like database names, API keys, or stage-specific settings directly into your code. Instead, you should use Environment Variables. This allows you to use the exact same code in dev, staging, and prod, but with different configurations.

Stages in SAM

In lesson 5, we added a StageName parameter. Now let's see how to pass that value down to our Rust code.

template.yaml
yaml
Parameters:
  StageName:
    Type: String
    Default: dev
 
Globals:
  Function:
    Runtime: provided.al2023
    Environment:
      Variables:
        STAGE: !Sub ${StageName}

The Globals section is a shortcut. By putting the Environment section here, every Lambda function in this project will automatically get an environment variable named STAGE.

Reading Variables in Rust

In Rust, we use the standard library std::env to read these variables.

Update your handler in src/bin/GreetingManager.rs:

src/bin/GreetingManager.rs
rust
use std::env;
 
async fn handler(
    event: LambdaEvent<ApiGatewayProxyRequest>,
) -> Result<ApiGatewayProxyResponse, Error> {
    // Read the STAGE variable. Default to "dev" if it's missing.
    let stage = env::var("STAGE").unwrap_or_else(|_| "dev".to_string());
 
    let (method, resource) = get_route(&event.payload);
 
    match (method.as_str(), resource.as_str()) {
        ("GET", "/hello") => {
            let message = format!("Hello from the {} environment!", stage);
            Ok(create_api_response(200, &message, Some(())))
        }
        // ...
    }
}

Why use stages?

Imagine you have a Users table in DynamoDB.

  • In dev, the table is named users-dev.
  • In prod, the table is named users-prod.

Instead of checking if stage == "prod" in your Rust code, you can just pass the table name as an environment variable in template.yaml:

template.yaml
yaml
Environment:
  Variables:
    USER_TABLE: !Sub "users-${StageName}"

Then in Rust:

rust
Source File
let table_name = env::var("USER_TABLE").expect("USER_TABLE not set");

This keeps your Rust code clean and "agnostic" of the environment it's running in.

If you try to read a variable that doesn't exist, env::var will return an Err.

  • Use .expect("msg") if the variable is required (the Lambda will crash if it's missing, which is usually better than running with bad config).
  • Use .unwrap_or(...) if the variable has a sensible default.

Pro Tip

You can also use a crate like dotenvy for local development, but for AWS Lambda, the built-in std::env is all you need.

Our configuration is solid, but what happens when something actually goes wrong? In the next post, we'll dive deep into Error Handling the "Rust Way" to make our API bulletproof.