Röviden: Egy teljes, production-kész serverless API felépítése lépésről lépésre: HTTP API, Lambda-integráció, DynamoDB single-table modell, IAM least-privilege és megfigyelhetőség — minden sorban Terraformmal.
Egy serverless REST API-t sokan „pár kattintásból” állítanak össze a konzolon, aztán fél év múlva senki nem tudja, mi hogyan lett bekötve. Ebben a cikkben végigépítünk egy teljes, reprodukálható API-t Terraformmal: HTTP API a belépési pont, Lambda a logika, DynamoDB az adat, IAM a szigorú jogosultság, és CloudWatch a láthatóság. A cél nem a leggyorsabb demó, hanem egy olyan alap, amit másfél év múlva is bátran módosítasz.
A cél-architektúra
A kliens egy HTTP API-hoz beszél, ami minden útvonalat egyetlen Lambda-függvényhez proxyzik (Lambda proxy integráció). A függvény egy DynamoDB táblát olvas és ír single-table modellben. A jogosultságokat egy szűkre szabott IAM-role adja, a naplók és metrikák a CloudWatchba folynak.
Miért HTTP API és nem REST API?
Az API Gateway kétféle terméket kínál: a régebbi REST API-t és az újabb HTTP API-t. A legtöbb új projektnek a HTTP API a jó választás: olcsóbb, alacsonyabb a latenciája, és natívan támogatja a JWT-alapú authorizert. A REST API akkor indokolt, ha API-kulcsokra, use-plan alapú kvótára, request/response transzformációra vagy WAF-integrációra van szükséged közvetlenül a gatewayen.
| Szempont | HTTP API | REST API |
|---|---|---|
| Ár (millió kérésenként) | alacsonyabb | magasabb |
| Latencia | kisebb overhead | nagyobb |
| JWT authorizer | natív | Lambda authorizer kell |
| Request/response mapping | korlátozott | teljes |
| API-kulcs + usage plan | nincs | van |
| Mikor válaszd? | új, egyszerű JSON API | legacy, kvóta, transzformáció |
A DynamoDB adatmodell
A single-table design lényege, hogy egyetlen táblában, generikus PK/SK kulcsokkal tárolunk több entitástípust. Egy egyszerű „feladatok egy felhasználóhoz” modellnél a partíciós kulcs a felhasználó, a rendezési kulcs pedig a feladat azonosítója.
| Entitás | PK | SK | további attribútumok |
|---|---|---|---|
| Feladat | USER#42 | TASK#01H... | title, done, createdAt |
| Felhasználó | USER#42 | PROFILE | email, name |
Így egyetlen Query-vel lekérhető egy felhasználó összes feladata (PK = USER#42 AND begins_with(SK, 'TASK#')), és pont-lekéréssel egy konkrét feladat is.
A Terraform alap: provider és tábla
Kezdjük a provider rögzítésével és a DynamoDB táblával. On-demand (PAY_PER_REQUEST) kapacitást használunk, hogy ne kelljen kapacitást tervezni — kiszámíthatatlan terhelésnél ez a biztonságos alapértelmezés.
terraform {
required_version = ">= 1.9"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.60"
}
}
}
provider "aws" {
region = var.region
}
resource "aws_dynamodb_table" "tasks" {
name = "${var.project}-tasks"
billing_mode = "PAY_PER_REQUEST"
hash_key = "PK"
range_key = "SK"
attribute {
name = "PK"
type = "S"
}
attribute {
name = "SK"
type = "S"
}
point_in_time_recovery {
enabled = true
}
tags = local.tags
}A Lambda és a szigorú IAM-role
A függvény kap egy role-t, ami CSAK az adott tábla olvasására/írására jogosít — nem dynamodb:* az egész fiókon. Ez a least-privilege elv, és Terraformban pont ilyen jól látszik, mit engedünk meg.
resource "aws_iam_role" "lambda" {
name = "${var.project}-lambda"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = { Service = "lambda.amazonaws.com" }
}]
})
}
data "aws_iam_policy_document" "table_access" {
statement {
actions = [
"dynamodb:GetItem",
"dynamodb:PutItem",
"dynamodb:Query",
"dynamodb:UpdateItem",
"dynamodb:DeleteItem",
]
resources = [aws_dynamodb_table.tasks.arn]
}
}
resource "aws_iam_role_policy" "table_access" {
name = "table-access"
role = aws_iam_role.lambda.id
policy = data.aws_iam_policy_document.table_access.json
}
resource "aws_iam_role_policy_attachment" "logs" {
role = aws_iam_role.lambda.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}
resource "aws_lambda_function" "api" {
function_name = "${var.project}-api"
role = aws_iam_role.lambda.arn
runtime = "nodejs20.x"
handler = "index.handler"
filename = data.archive_file.lambda.output_path
source_code_hash = data.archive_file.lambda.output_base64sha256
timeout = 10
memory_size = 256
environment {
variables = {
TABLE_NAME = aws_dynamodb_table.tasks.name
}
}
tags = local.tags
}A HTTP API bekötése
A HTTP API-t egy aws_apigatewayv2_api írja le, a Lambda-integrációt pedig egy proxy-route. A $default route mindent a függvényhez irányít — a routingot magában a kódban intézzük, ami egyszerű API-nál teljesen elfogadható.
resource "aws_apigatewayv2_api" "http" {
name = "${var.project}-http"
protocol_type = "HTTP"
}
resource "aws_apigatewayv2_integration" "lambda" {
api_id = aws_apigatewayv2_api.http.id
integration_type = "AWS_PROXY"
integration_uri = aws_lambda_function.api.invoke_arn
payload_format_version = "2.0"
}
resource "aws_apigatewayv2_route" "default" {
api_id = aws_apigatewayv2_api.http.id
route_key = "$default"
target = "integrations/${aws_apigatewayv2_integration.lambda.id}"
}
resource "aws_apigatewayv2_stage" "default" {
api_id = aws_apigatewayv2_api.http.id
name = "$default"
auto_deploy = true
}
resource "aws_lambda_permission" "apigw" {
statement_id = "AllowAPIGatewayInvoke"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.api.function_name
principal = "apigateway.amazonaws.com"
source_arn = "${aws_apigatewayv2_api.http.execution_arn}/*/*"
}A aws_lambda_permission gyakran elfelejtett, de kötelező lépés: e nélkül a gateway nem hívhatja a függvényt, és 500-as hibát kapsz látszólag ok nélkül.
A kérés útja végponttól végpontig
Érdemes fejben tartani, mi történik egyetlen kéréssel — ez segít a hibakeresésben, amikor valami nem úgy megy, ahogy várod.
Költség: mit fizetsz valójában?
Serverlessnél a vonzó rész, hogy tétlenül szinte semmibe nem kerül. Egy közepes forgalmú API havi költsége jellemzően a kérésszám és a Lambda futásidő függvénye — nem előre lefoglalt kapacitásé.
| Komponens | Elszámolás | Megjegyzés |
|---|---|---|
| HTTP API | kérésenként | nincs alapdíj |
| Lambda | GB-másodperc + kérés | tétlenül 0 |
| DynamoDB (on-demand) | olvasott/írt egységenként | nincs kapacitástervezés |
| CloudWatch | napló-GB + metrika | a log-retention állítsd be! |
Mit jelent ez neked?
Ez az öt erőforrás — tábla, role, policy, függvény, API — egy teljes, reprodukálható serverless backend. A lényeg nem a méret, hanem hogy minden döntés látszik a kódban: mit engedsz meg (IAM), hogyan skálázódik (on-demand), és hogyan figyeled (CloudWatch). Ha innen indulsz, a következő lépés a JWT-authorizer, a több route és a CI/CD — de az alap már production-kész, verziózható és auditálható. A terraform apply mindig ugyanazt adja, és ez a nyugalom a serverless igazi haszna.