2025-09-04
Migrating from Serverless Framework to AWS CDK: Part 1 - Why Make the Switch?
Explore the motivations behind migrating from Serverless Framework to AWS CDK, including licensing changes, architectural advantages, and when CDK becomes the better choice for your serverless applications.
Serverless Framework and AWS CDK solve overlapping problems with different philosophies: Serverless Framework is a YAML-driven, provider-neutral abstraction over a Lambda-centric deployment unit; CDK is a typed, AWS-native, programmatic synthesis layer over CloudFormation. When Serverless Framework introduced paid licensing, the decision to migrate became an active trade-off rather than a default, and the choice between the two depends less on licensing cost than on team comfort with imperative-versus-declarative infrastructure, type safety requirements, and willingness to accept AWS lock-in in exchange for deeper native coverage.
This six-part series covers the practical migration from Serverless Framework to CDK. This first post covers the architectural trade-offs, the migration scope assessment, and the stack-by-stack plan that keeps production running throughout.
This six-part series covers the complete migration process:
- Part 1: Why migrate? Understanding the trade-offs (this post)
- Part 2: Setting up your CDK environment and project structure
- Part 3: Migrating Lambda functions and API Gateway
- Part 4: Database resources and environment management
- Part 5: Authentication, authorization, and IAM
- Part 6: Migration strategies and best practices
Understanding the Migration Motivations
Migrating infrastructure tools isn’t just about licensing costs. Here are the key factors that typically drive teams toward CDK:
Direct Cost Considerations
Serverless Framework licensing (for teams using Pro features):
- Per-deployment pricing model
- Scaling costs with team growth
- Additional features behind paid tiers
CDK approach:
- No licensing fees (part of AWS CLI)
- Infrastructure as standard application code
- Native AWS service support
Hidden Operational Costs
Working with both tools revealed several operational differences:
- YAML maintenance: Configuration syntax can become complex
- Plugin dependencies: Third-party plugin compatibility issues
- Cross-service references: String-based references vs. typed objects
- Debugging: Runtime vs. compile-time error detection
These factors matter more than direct costs for most applications.
Key Technical Differences That Matter
Through practical experience, several technical differences became apparent that influence the migration decision:
1. Configuration vs. Code
Serverless Framework approach (YAML configuration):
# serverless.yml
provider:
environment:
STRIPE_API_KEY: ${env:STRIPE_API_KEY}
STRIPE_WEBHOOK_SECRET: ${env:STRIPE_WEBHOOK_SECRET}
CDK approach (TypeScript code):
// Environment variables are validated at compile time
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET;
if (!webhookSecret) {
throw new Error('STRIPE_WEBHOOK_SECRET environment variable required');
}
Key insight: Configuration typos can reach production with YAML, while TypeScript catches issues at compile time.
2. Plugin Dependencies vs. Native Integration
Serverless Framework relies on community plugins for advanced features:
plugins:
- serverless-webpack
- serverless-offline
- serverless-step-functions
custom:
webpack:
webpackConfig: webpack.config.js
CDK provides native constructs for AWS services:
// Native bundling and service integration
const bundling = {
target: 'node20',
minify: true,
sourceMap: true,
};
Learning: Plugin compatibility can become a maintenance burden during Node.js upgrades.
3. Cross-Stack References
Serverless Framework uses CloudFormation exports and string interpolation:
# auth-service/serverless.yml
provider:
environment:
USER_TABLE_ARN: ${cf:database-stack-${opt:stage}.UserTableArn}
CDK enables type-safe object references:
// Direct object references with compile-time validation
const authStack = new AuthStack(this, 'AuthStack', {
userTable: databaseStack.userTable, // TypeScript ensures this exists
});
Benefit: Refactoring becomes safer when dependencies are explicit and type-checked.
The TypeScript Infrastructure Advantage
Moving from YAML configuration to TypeScript code brings several advantages:
Serverless Framework (YAML configuration):
# serverless.yml
provider:
name: aws
runtime: nodejs20.x
environment:
TABLE_NAME: ${self:service}-${opt:stage}-users
functions:
createUser:
handler: src/handlers/users.create
events:
- http:
path: users
method: post
cors: true
CDK (TypeScript code):
// lib/api-stack.ts
import { RestApi, LambdaIntegration } from 'aws-cdk-lib/aws-apigateway';
import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs';
const createUserFn = new NodejsFunction(this, 'CreateUserFunction', {
entry: 'src/handlers/users.ts',
handler: 'create',
environment: {
TABLE_NAME: userTable.tableName,
},
});
// Type-safe integration
const api = new RestApi(this, 'UserApi');
api.root.addResource('users').addMethod('POST',
new LambdaIntegration(createUserFn)
);
Benefits include:
- Compile-time error detection
- IDE autocompletion
- Refactoring support
- Type-safe environment variables
Native AWS Service Integration
Serverless Framework requires plugins for advanced AWS services:
plugins:
- serverless-step-functions
- serverless-appsync-plugin
- serverless-plugin-aws-alerts
custom:
alerts:
stages:
- production
topics:
alarm:
topic: ${self:service}-${opt:stage}-alerts
CDK provides native constructs for all AWS services:
import { StateMachine } from 'aws-cdk-lib/aws-stepfunctions';
import { LambdaInvoke } from 'aws-cdk-lib/aws-stepfunctions-tasks';
import { GraphqlApi } from 'aws-cdk-lib/aws-appsync';
import { Alarm } from 'aws-cdk-lib/aws-cloudwatch';
// Direct service integration without plugins
const workflow = new StateMachine(this, 'UserWorkflow', {
definition: new LambdaInvoke(this, 'ProcessUser', {
lambdaFunction: processUserFn,
}),
});
const api = new GraphqlApi(this, 'UserGraphQL', {
name: 'user-api',
schema: SchemaFile.fromAsset('schema.graphql'),
});
Infrastructure Composition and Reusability
Serverless Framework uses includes and variables:
# serverless.yml
custom:
userTableConfig: ${file(./config/tables.yml):userTable}
resources:
Resources:
UserTable: ${self:custom.userTableConfig}
CDK enables true object-oriented infrastructure:
// lib/constructs/serverless-api.ts
export class ServerlessApi extends Construct {
public readonly api: RestApi;
public readonly functions: Map<string, NodejsFunction>;
constructor(scope: Construct, id: string, props: ServerlessApiProps) {
super(scope, id);
// Encapsulated, reusable infrastructure patterns
this.api = new RestApi(this, 'Api', {
restApiName: props.apiName,
deployOptions: this.createDeployOptions(props.stage),
});
this.functions = this.createFunctions(props.routes);
this.setupRoutes(props.routes);
this.setupAlarms(props.monitoring);
}
}
// Usage across multiple stacks
new ServerlessApi(this, 'UserApi', {
apiName: 'users',
routes: userRoutes,
monitoring: productionMonitoring,
});
Testing Infrastructure
Serverless Framework testing typically involves:
- Mocking framework behavior
- Testing deployed resources
- Limited unit testing options
CDK enables comprehensive infrastructure testing:
// test/api-stack.test.ts
import { Template } from 'aws-cdk-lib/assertions';
test('API Gateway has CORS enabled', () => {
const template = Template.fromStack(stack);
template.hasResourceProperties('AWS::ApiGateway::Method', {
Integration: {
IntegrationResponses: [{
ResponseParameters: {
'method.response.header.Access-Control-Allow-Origin': "'*'",
},
}],
},
});
});
test('Lambda has correct environment variables', () => {
template.hasResourceProperties('AWS::Lambda::Function', {
Environment: {
Variables: {
TABLE_NAME: { Ref: Match.anyValue() },
STAGE: 'production',
},
},
});
});
When Each Tool Excels
Choose CDK When You Need:
- Complex AWS service integration - Step Functions, EventBridge, AppSync
- Shared infrastructure patterns - Reusable constructs across teams
- Fine-grained control - Custom CloudFormation resources
- Strong typing - TypeScript throughout your stack
- Infrastructure testing - Unit and integration tests for IaC
- Large team coordination - Explicit dependencies and interfaces
Stay with Serverless Framework When:
- Simple Lambda + API Gateway - Basic CRUD APIs
- Existing plugin ecosystem - Heavy reliance on community plugins
- Team YAML preference - Developers uncomfortable with TypeScript
- Quick prototypes - Rapid proof-of-concepts
- Small applications - Minimal infrastructure complexity
Migration Complexity Assessment
Before migrating, evaluate your current setup:
interface MigrationComplexity {
functionCount: number;
customResources: boolean;
plugins: string[];
environments: number;
cicdIntegration: boolean;
}
function assessMigrationEffort(current: MigrationComplexity): string {
const pluginComplexity = current.plugins.filter(p =>
!['serverless-offline', 'serverless-webpack'].includes(p)
).length;
const score =
current.functionCount * 0.5 +
(current.customResources ? 20 : 0) +
pluginComplexity * 10 +
current.environments * 5 +
(current.cicdIntegration ? 15 : 0);
if (score < 30) return 'Low - 1-2 weeks';
if (score < 60) return 'Medium - 2-4 weeks';
return 'High - 1-2 months';
}
Migration Decision Framework
Based on experience with both tools, here’s a practical framework for evaluating migration:
Technical Assessment
Current infrastructure complexity:
- Number of Lambda functions and services
- Custom resources and CloudFormation usage
- Cross-service dependencies
- Plugin dependencies and maintenance overhead
Team Readiness
Skill evaluation:
- TypeScript experience level
- Infrastructure as code familiarity
- Available learning time
- Comfort with programmatic infrastructure
Migration Planning
Risk mitigation strategies:
- Gradual migration vs. full cutover
- Rollback procedures and testing
- Parallel infrastructure during transition
- Team training and knowledge transfer
Expected Benefits
Realistic outcome expectations:
- Improved developer experience with IDE support
- Better error detection at compile time
- Simplified cross-service references
- Enhanced testing capabilities for infrastructure
Migration Readiness Checklist
Before starting a migration, consider these factors:
Technical Readiness
- Team has TypeScript experience or learning time
- Current infrastructure is well-documented
- Plugin dependencies are understood and replaceable
- Testing strategy exists for infrastructure changes
Organizational Readiness
- Migration timeline aligns with business goals
- Rollback procedures are defined
- Knowledge transfer plan exists
- Migration complexity is appropriate for team size
When NOT to Migrate
Stick with Serverless Framework if:
- Limited TypeScript experience - The learning curve may impact delivery
- Simple, stable applications - Migration overhead may not be justified
- Heavy plugin dependencies - Ensure CDK alternatives exist
- Time constraints - Migration requires dedicated focus and time
What’s Next
Once you’ve decided to migrate, the real work begins: setting up a CDK project structure that supports safe, gradual migration.
In Part 2, I’ll cover the practical setup steps that enable successful migration. We’ll explore project architecture patterns, development workflows, and environment configuration that make the transition manageable.
The technical migration is often easier than the process challenges - coordinating team efforts, maintaining development velocity, and ensuring production stability throughout the transition require careful planning.
Migrating from Serverless Framework to AWS CDK
A comprehensive 6-part guide covering the complete migration process from Serverless Framework to AWS CDK, including setup, implementation patterns, and best practices.
All posts in this series
Related posts
Deep dive into migrating Lambda functions, API Gateway configurations, request validations, and error handling from Serverless Framework to AWS CDK with practical examples.
Master DynamoDB migrations, environment variable management, secrets handling, and VPC configurations when moving from Serverless Framework to AWS CDK.
Named signals that justify a Kafka migration from a managed event bus, and a four-phase outbox-anchored playbook to move without rip-and-replace.
A CDK guide for deploying a minimal Strands agent on AgentCore Runtime — parameterized stack, arm64 build, deploy and invoke, and the IAM and Marketplace prerequisites you need before the first call.
A comprehensive technical guide to choosing and implementing AWS edge computing solutions for global applications with practical examples and cost optimization strategies.