Start by identifying how the Lambda is invoked
The first migration question is not the programming language.
It is:
What creates the Lambda event object?
Common HTTP paths are:
Lambda Function URL
AWS Lambda Function URLs expose a dedicated HTTPS endpoint and currently use the API Gateway payload format version 2.0 for request and response mapping.
The Lambda handler receives an event with fields such as:
{
"version": "2.0",
"rawPath": "/users/123",
"rawQueryString": "expand=true",
"headers": {
"content-type": "application/json"
},
"requestContext": {
"http": {
"method": "GET",
"path": "/users/123"
}
}
}
API Gateway HTTP API
API Gateway Lambda proxy integrations can use payload format 1.0 or 2.0.
That means two Lambda handlers that both look like "HTTP functions" may read request data differently.
Before migration, record:
- Function URL vs API Gateway;
- payload format version;
- route/path handling;
- query-string behavior;
- cookie behavior;
- CORS behavior;
- authentication/authorizer dependencies;
- binary/base64 response handling.
Do not port code until this input contract is understood.
The main code change is usually event-object removal
Consider a simple Node.js Lambda Function URL handler:
export const handler = async (event) => {
const name =
event.queryStringParameters?.name || "world";
return {
statusCode: 200,
headers: {
"content-type": "application/json"
},
body: JSON.stringify({
message: `hello ${name}`
})
};
};
The business logic is simple:
read name
→ create JSON response
What is AWS-specific is:
event.queryStringParameters;
- Lambda's
statusCode/headers/body response envelope;
- exported Lambda handler signature.
The migration becomes easier when that platform-specific layer is separated from business logic.
Refactor business logic before moving providers
A useful first step is:
function createGreeting(name = "world") {
return {
message: `hello ${name}`
};
}
The Lambda adapter becomes:
export const handler = async (event) => {
const result = createGreeting(
event.queryStringParameters?.name
);
return {
statusCode: 200,
headers: {
"content-type": "application/json"
},
body: JSON.stringify(result)
};
};
Once the core logic no longer understands Lambda events, moving the HTTP adapter is much less risky.
This is the same portability principle covered in Portable Serverless Handlers: Avoiding Lambda Lock-In.
Node.js: Lambda handler to standard HTTP server
Raff Functions supports standard Node.js HTTP handlers.
The equivalent standard Node.js application can look like:
import http from "node:http";
function createGreeting(name = "world") {
return {
message: `hello ${name}`
};
}
const server = http.createServer((req, res) => {
const url = new URL(
req.url,
`http://${req.headers.host}`
);
if (req.method !== "GET" || url.pathname !== "/") {
res.writeHead(404, {
"content-type": "application/json"
});
return res.end(
JSON.stringify({ error: "not found" })
);
}
const result = createGreeting(
url.searchParams.get("name") || "world"
);
res.writeHead(200, {
"content-type": "application/json"
});
res.end(JSON.stringify(result));
});
server.listen(
process.env.PORT || 3000,
"0.0.0.0"
);
The business logic did not change.
What changed was the provider adapter:
Lambda event
→ Node request
Lambda response envelope
→ normal HTTP response
This is the preferred migration shape: remove AWS-specific request plumbing while preserving application behavior.
Python: Lambda adapter to FastAPI / ASGI
A Python Lambda may start like:
import json
def handler(event, context):
name = (
event.get("queryStringParameters") or {}
).get("name", "world")
return {
"statusCode": 200,
"headers": {
"content-type": "application/json"
},
"body": json.dumps({
"message": f"hello {name}"
})
}
Extract the business logic:
def create_greeting(name="world"):
return {
"message": f"hello {name}"
}
Then expose it through FastAPI:
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def root(name: str = "world"):
return create_greeting(name)
Raff Functions supports FastAPI/ASGI, so the handler becomes a normal web application rather than a Lambda event adapter.
This also makes local testing easier because the same HTTP contract can run outside the provider.
Build a request-mapping checklist
Before removing the Lambda adapter, map every event field the function uses.
Common examples:
| Lambda/API Gateway input | Standard HTTP equivalent |
|---|
requestContext.http.method | HTTP method |
rawPath / path | Request path |
queryStringParameters | URL query parameters |
headers | Request headers |
cookies | Cookie header / framework cookie API |
body | Request body |
isBase64Encoded | Binary/body decoding responsibility |
| authorizer context | Replacement auth middleware / trusted headers |
Do not assume API Gateway 1.0 and 2.0 behave identically.
AWS documents differences around cookies, multi-value headers, multi-value query strings, and path handling.
If your Lambda depends on these details, test them explicitly during migration.
Remove the Lambda response envelope
A common migration bug is leaving Lambda response JSON inside the new HTTP application.
For example, this is correct Lambda-style output:
{
"statusCode": 201,
"headers": {
"content-type": "application/json"
},
"body": "{\"id\":123}"
}
But in a standard HTTP runtime, returning that whole object as JSON can accidentally produce:
HTTP/1.1 200 OK
{
"statusCode": 201,
"headers": {...},
"body": "{\"id\":123}"
}
The correct standard HTTP behavior is:
HTTP/1.1 201 Created
Content-Type: application/json
{"id":123}
During migration, convert:
- Lambda
statusCode → actual HTTP status;
- Lambda
headers → actual response headers;
- Lambda
body → actual response body;
- Lambda cookies → framework/server cookie API.
CORS moves out of API Gateway or Function URL configuration
AWS Function URLs and API Gateway can apply CORS outside the application.
If the old deployment depends on provider-side CORS settings, moving only the function code may silently remove that behavior.
Inventory:
- allowed origins;
- allowed methods;
- allowed headers;
- credentials;
- preflight behavior;
- exposed headers.
On Raff, implement CORS through the application/framework or another verified edge layer.
Do not use Access-Control-Allow-Origin: * with credentials.
For broader API security, use Serverless API Security: Authentication, CORS & Rate Limiting.
Authentication is often the hardest migration dependency
Some HTTP Lambdas rely on:
- API Gateway Lambda authorizers;
- Cognito;
- IAM/SigV4;
- JWT authorizers;
- custom headers inserted by upstream infrastructure.
Those are not part of the function's business code, but they are part of the production request path.
Before cutover, answer:
- Who authenticates the caller today?
- Where is the identity verified?
- What claims reach the Lambda?
- What authorization decision is made inside the function?
- What replaces the AWS-specific identity layer?
Do not make the new Raff Function public while assuming the old API Gateway authorizer still protects it.
If the function should be public only to a webhook provider, use signature verification rather than relying on URL secrecy.
Environment variables migrate easily—AWS service credentials may not
Simple configuration typically maps directly:
AWS Lambda environment variable
→ Raff Function environment variable / secret
Examples:
APP_ENV;
- feature flags;
- upstream API base URL;
- application secret.
AWS-integrated credentials require more work.
A Lambda often accesses S3, DynamoDB, SQS, Secrets Manager, or another AWS service through its execution role.
That means there may be no explicit credential in the application configuration.
When migrating, inventory every AWS SDK call.
For each dependency, choose one of three paths:
- keep calling the AWS service from Raff;
- migrate the dependency to a Raff or third-party equivalent;
- abstract the dependency behind an application adapter.
Do not discover after cutover that the function depended on an IAM execution role that no longer exists.
Object storage dependency: S3 to Raff Object Storage
If the HTTP Lambda reads or writes S3 objects, Raff Object Storage is S3-compatible.
That can reduce migration work, but endpoint, bucket, region, and credential configuration still need review.
A useful application boundary is:
objectStore.get(key)
objectStore.put(key, body)
rather than scattering AWS SDK calls throughout business logic.
Raff Functions supports scoped per-function Object Storage bindings, allowing a function to receive limited bucket credentials without embedding primary credentials in source code.
Database dependency: replace IAM assumptions explicitly
Lambda database access may use:
- static credentials;
- AWS Secrets Manager;
- RDS Proxy;
- IAM database authentication;
- VPC networking.
A Raff Function can bind to supported Raff Managed Databases with scoped per-function credentials.
But this is not automatically equivalent to RDS Proxy or IAM database authentication.
For each database dependency, re-evaluate:
- connection limits;
- pooling;
- TLS;
- secrets;
- private/public connectivity;
- retry behavior;
- transaction semantics.
Do not treat "the SQL is the same" as a complete database migration.
Logging: CloudWatch-specific assumptions need removal
A Lambda may rely on CloudWatch Logs implicitly through console.log or Python logging.
Raff Functions also exposes logs and metrics, but you should remove code that assumes CloudWatch log-group names, AWS request IDs, or CloudWatch-specific query/alert integrations unless you intentionally keep those external systems.
Preserve useful application-level fields such as:
- request ID;
- user/account ID where appropriate;
- operation name;
- duration;
- error type;
- business event ID.
Avoid logging secrets or authorization headers.
Replace Lambda context dependencies
Lambda handlers can use the context object for:
- request ID;
- remaining execution time;
- function metadata.
Search the old code for:
context.
awsRequestId
getRemainingTimeInMillis
functionName
functionVersion
Decide whether each value is:
- business-critical;
- observability-only;
- removable;
- replaceable with application-generated metadata.
A portable application should not require an AWS request ID to function correctly.
Timeouts should be re-evaluated, not copied
AWS Lambda currently has a hard maximum execution duration of 15 minutes.
Raff Functions supports much longer runs: up to one hour by default and up to 24 hours on request.
That does not mean every migrated HTTP function should receive a one-hour timeout.
For normal APIs:
- keep request handling bounded;
- move long jobs to background/event workflows;
- define client timeouts;
- avoid holding HTTP connections unnecessarily.
Longer platform limits are a capability, not a target.
Use Long-Running Serverless Functions for AI, ETL, and Batch Jobs for workloads that genuinely need longer execution.
Function URL migration should preserve endpoint behavior
If the AWS workload uses a Lambda Function URL, record:
- path;
- query parameters;
- method behavior;
- CORS;
- authentication mode;
- custom domain/proxy in front;
- response codes;
- headers;
- cookies;
- redirects.
Raff Functions provides an HTTP live URL with automatic TLS.
Before DNS or client cutover, run the same request suite against both endpoints.
Example test matrix:
| Test | AWS endpoint | Raff endpoint |
|---|
| GET / | 200 | 200 |
| GET /?name=batu | expected JSON | same JSON |
| invalid path | 404 | 404 |
| malformed JSON | 400 | 400 |
| auth missing | 401/403 | equivalent |
| OPTIONS/CORS | expected headers | equivalent |
The responses do not need byte-for-byte identical infrastructure headers.
They do need equivalent application behavior.
API Gateway migrations need a route inventory
API Gateway may provide routing that the Lambda itself never sees as configuration.
Inventory every route:
GET /users/{id}
POST /users
DELETE /users/{id}
Then decide whether Raff should run:
- one function handling all routes;
- separate functions;
- a standard web framework/router.
For most small HTTP APIs, moving routing into FastAPI, Node, Go, or another standard framework improves portability.
Do not create one function per route simply because API Gateway originally did.
Keep traffic cutover reversible
A safe migration should be staged.
Phase 1 — Refactor
Extract business logic from Lambda event/context assumptions while still running on AWS.
Phase 2 — Deploy Raff Function
Deploy the standard HTTP handler.
Phase 3 — Shadow or test
Send representative requests to Raff and compare behavior.
Phase 4 — Move noncritical traffic
Switch a test client, staging hostname, or limited integration.
Phase 5 — Production cutover
Update DNS, client configuration, webhook destination, or API endpoint.
Phase 6 — Observe
Watch:
- error rate;
- latency;
- logs;
- downstream database/storage calls;
- authentication failures;
- unexpected retries.
Phase 7 — Retain rollback
Keep the AWS Lambda available until the agreed rollback window closes.
Do not delete AWS infrastructure immediately after the first successful Raff request.
Pricing migration should use workload measurements
AWS Lambda and Raff Functions meter different dimensions.
AWS Lambda pricing can include:
- requests;
- compute duration/memory;
- architecture/runtime-dependent compute;
- data transfer and adjacent AWS services.
Raff Functions currently meters:
- memory GB-seconds;
- active CPU vCPU-seconds.
Requests and function egress are currently free, and a spend cap is enabled by default.
Use actual workload data:
- invocations;
- memory;
- CPU time;
- average and p95 duration;
- egress;
- concurrency.
Do not compare only headline per-request prices.
For the provider-level comparison, use AWS Lambda Alternative 2026: Pricing, Performance & Features.
HTTP Lambda migration checklist
Before cutover:
- invocation path is known;
- API Gateway payload format is known;
- every used event field is mapped;
- business logic is isolated from Lambda adapter code;
- Lambda response envelope is removed;
- CORS behavior is reproduced;
- authentication/authorizer behavior is replaced;
- environment variables are migrated;
- AWS SDK dependencies are inventoried;
- IAM execution-role assumptions are replaced;
- storage/database dependencies are verified;
- logging is provider-neutral;
- timeout behavior is intentional;
- HTTP test suite passes against Raff;
- rollback endpoint remains available;
- production cutover is observable.
Frequently asked questions
Can I run an AWS Lambda handler unchanged on Raff Functions?
Usually not if it depends on the Lambda event and context objects. Raff Functions uses standard HTTP/runtime handlers, so provider-specific request/response adapters should be removed.
Do I need to rewrite the business logic?
Often no. If business logic is separated from Lambda event parsing, most of the change can be limited to the HTTP adapter and AWS-specific service integrations.
What happens to the Lambda statusCode/body response object?
Replace it with a real HTTP status, headers, and response body using the standard server or framework API.
Can Raff Functions replace Lambda Function URLs?
For normal HTTP workloads, Raff Functions provides a live HTTPS URL. You still need to migrate CORS, authentication, routing, headers, and any AWS-specific behavior explicitly.
Can I keep using AWS services from Raff Functions?
Yes if the service exposes an API reachable from the function and you configure appropriate credentials/network access. Whether you should keep or migrate each dependency is a separate architecture decision.
Does Raff Functions support long-running code?
Yes. Raff currently supports up to one hour by default and up to 24 hours on request. Normal HTTP endpoints should still remain bounded.
How should I migrate API Gateway authorizers?
Treat authentication as a separate dependency. Replace the authorizer with application middleware, another identity layer, signed requests, JWT verification, or another design that matches the workload.
Sources