Welcome to the first post in a series where I’ll walk through building a CRM (Customer Relationship Management) system using AWS SAM. We’ll progressively create a serverless backend, focusing on clean architecture, scalability, and cost optimization.
In this post, we'll set up our project and create a /ping
health check endpoint to make sure everything is working.
Why Serverless and SAM?
One of the biggest benefits of serverless is that you don't need to worry about infrastructure maintenance, instead you assemble your system with serverless primitives that can scale based on demand, and therefore, you pay only for the resources you use.
AWS SAM (Serverless Application Model) simplifies serverless app deployment. SAM consists of the SAM CLI that helps test, build and deploy, and also SAM templates, which offers abstractions on top of the AWS Cloudformation syntax.
Project Setup
Let’s start by creating the simplest piece of functionality: a ping endpoint that returns "pong"
. This will serve as our health check route and confirm everything is wired correctly.
Folder structure:
├─ src/
│ └─ functions/
│ └─ ping/ # Ping function code
│ ├─ __init__.py
│ └─ app # Ping function module
│ ├─ __init__.py
│ └─ main.py # Lambda handler
├─ samconfig.toml # SAM configuration
└─ template.yml # SAM template
We'll start with the most important piece of the puzzle, the SAM template, where we define all the infrastructure components our application will have, all defined as code (IaC), that means that we can version and track infrastructure changes in our git repository.
# template.yml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: CRM SAM
Resources:
ApiGateway:
Type: AWS::Serverless::Api
Properties:
StageName: prod
PingFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: src/functions/ping/
Handler: app.main.lambda_handler
Runtime: python3.13
Events:
Ping:
Type: Api
Properties:
RestApiId: !Ref ApiGateway
Path: /ping
Method: GET
Outputs:
PingUrl:
Description: URL to ping endpoint
Value: !Sub "https://${ApiGateway}.execute-api.${AWS::Region}.amazonaws.com/prod/ping"
In this template we can see we're defining the Resources
we're gonna be using, which in this case are:
-
ApiGateway
: anAWS::Serverless::Api
that receives requests to be resolved by a lambda function, and then takes its response and hands it back to the caller. -
PingFunction
: anAWS::Serverless::Function
that will be resolving the ping request and will create the response to be then returned by the API Gateway.
Here we can already notice a common theme, all resources are comprised by a Type
which let's us define what kind of resource we're talking about, and Properties
which corresponds to the configuration we'll be giving to that particular resource.
At the bottom we can also notice the Outputs
section in which we can get different values from the resources that get generated at deployment time, which can be for reference or which can also be passed to other stacks in case we're building something more complex that involves multiple templates, in this case we're outputing the url we can hit to reach the ping function.
Now here's the code of the Lambda function:
# src/functions/ping/app/main.py
def lambda_handler(event, context):
return {
"statusCode": 200,
"headers": {"Content-Type": "application/json"},
"body": '{"response": "pong"}'
}
Very minimal, just returns a status code 200 and "pong"
in the body.
Finally, we have the samconfig.toml
file where we configure parameters for different sam
commands.
version = 0.1
[default]
[default.global.parameters]
stack_name = "crm-sam"
region = "us-east-1"
[default.build.parameters]
cached = true
parallel = true
[default.validate.parameters]
lint = true
[default.local_start_api.parameters]
warm_containers = "EAGER"
tags = "application=crm-sam"
[default.local_invoke.parameters]
tags = "application=crm-sam"
[default.logs.parameters]
tail = true
[default.deploy.parameters]
capabilities = ["CAPABILITY_IAM"]
confirm_changeset = false
resolve_s3 = true
s3_prefix = "crm-sam"
image_repositories = []
Running it locally.
Before running anything you always need to build the application:
sam build
Then you can run the project locally by using:
sam local start-api
and then if you curl
the endpoint with:
curl http://localhost:3000/ping
you should get the following response
{"response": "pong"}
Finally, you can deploy it to AWS by just running
sam deploy
at the end of the deployment output you should see something like:
CloudFormation outputs from deployed stack
-------------------------------------------------------------------
Outputs
-------------------------------------------------------------------
Key PingUrl
Description URL to ping endpoint
Value https://uywf3xcoej.execute-api.us-east-1.amazonaws.com/prod/ping
-------------------------------------------------------------------
which you can also curl
curl https://uywf3xcoej.execute-api.us-east-1.amazonaws.com/prod/ping
{"response": "pong"}
What did we learn?
- How to scaffold a minimal SAM project
- How to define a Lambda function
- How to expose a route using API Gateway
- How to test it locally and deploy it
This simple GET /ping
endpoint is the starting point of our CRM, from here, we'll expand into real CRM functionality.
I'll keep the code for this series in this repo, and the code for this post can be found here.
Coming Next
In the next post, we will create the first feature of our CRM: a POST /contacts
endpoint to store contact information in AWS DynamoDB.
Top comments (2)
When can we expect your next post? Do you have a github repo to go with it?
Hey there, I'll be keeping the code in this repo and I'll have the progress of each post in different branches. I will publish the next post in a few days.
Thanks for asking!