In the Building APIs with the AWS CDK + Lerna + Webpack: Part 1 in this two-part series, we leveled-up on AWS CDK fundamentals and built a CDK Stack that deploys a basic API Gateway + Lambda + DynamoDB API to receive orders from a (fake) ecommerce website. We deployed our code with the CDK CLI, made a call to our POST /checkout
API endpoint and then… it broke.
In this article, we’re going to fix the API! We’ll also cover how two popular code management utilities, Lerna and Webpack, can be used to better manage the AWS CDK code used to deploy your back-end services. We’ll start where we left off in the first article and refactor the code in a few steps.
Better Code Organization With Lerna
Lerna makes managing large TypeScript/JavaScript codebases easier by helping you split your code into independently-maintainable packages within a single git repository. Many popular open-source projects, including Create React App, React Router, and Babel take advantage of Lerna to manage versioning and publication of packages to the NPM registry. Fullstory uses Lerna to ensure a clean separation of concerns among our core app and recording code packages while also allowing us to easily run common commands across them.
While we’re certainly not working with a large codebase in our Reactshoppe API example, I’d still like to package the code up in a more meaningful way while working towards fixing our show-stopping bug.
We’ll do this in three steps:
Install Lerna
Move some files around
Update references
Step 1 - Install Lerna
Run npx lerna init
in our root project directory. Once done, you’ll see an empty “packages” directory and a lerna.json file. Next, we’re going to run npx lerna create [package name] --private
for each of the following packages we want to create:
reactshoppe-database - the CDK Construct that creates our DynamoDB Order table
reactshoppe-api - the CDK Construct that creates our API Gateway and binds API endpoints to our Lambda functions
functions - the Lambda functions that handle API requests
stack - The CDK Stack that deploys the database, API Gateway, and Lambda functions
The lerna create
command adds a little more cruft than I’d like for this example, so I’ve removed the __tests__
and lib
directories and everything below the "main"
field in the package.json
file for each package. You can see the changes from installing Lerna and creating our four packages here.
Step 2 - Move Some Files Around
Let’s move the DynamoDB Construct, API Gateway Construct, AWS Lambda source code, and Stack definitions out of ./lib
and into their requisite package directories. There’s not much to this, but feel free to check out the results in this commit.
Our updated project structure looks like this:
Code is organized into 4 functional areas under the packages directory
Step 3 - Update References
Almost there! We need to update dependencies between packages to use the local Lerna-managed packages and update our code to reference package names rather than file paths.
Start by updating dependencies between packages. We’ve got a dependency in functions
on reactshoppe-database
and a pair of dependencies in stack
on reactshoppe-api
and reactshoppe-database
. We can create these dependencies by invoking a few lerna add
commands:
npx lerna add reactshoppe-database --scope=functions
npx lerna add reactshoppe-api --scope=stack
npx lerna add reactshoppe-database --scope=stack
Lerna creates the appropriate symlinks in each packages’ respective node_modules
directory and updates each package.json
file. From now on, when we run npx lerna bootstrap
, the dependencies between local packages will be linked. We can also update the references to these packages in our code to use the package name, without having to spell out the relative file path. Check out this commit to see the differences after package dependencies have been added via Lerna and this one for the requisite changes to references in the code.
The npx cdk deploy
command will follow the symlinks created by Lerna when creating the Lambda deployment package that is uploaded to AWS. This ensures that all code is available to our Lambda function at runtime.
Now our bug is fixed! The reactshoppe-database module is available at runtime when the POST /checkout
request is handled by our Lambda function. Further, our code is now nicely organized into packages that encapsulate the components of our API. But there’s still a little more optimization we can do...
Enter Webpack
Wepback is the workhorse bundler that JavaScript developers have been using for years to roll complex build-time dependency-graphs of file relationships into static assets that can be served up for web applications. It can also minify code, making for smaller overall bundle sizes in production systems. This is helpful when building Lambda deployment packages since large unbundled dependencies can have a negative impact on cold start times for node.js Lambda functions. We’ll use Webpack to both minify and bundle the Lambda function code.
After adding the following webpack.config.js
file to packages/function
...we’ll update our npm scripts in package.json
to include Webpack in the build process:
And finally we’ll update the reactshoppe-api Construct to point to the “entrypoint” file created by our Webpack configuration:
And we’re done! Check out some existing resources AWS has put together about Webpack implementations for AWS services.
Trifecta
The AWS CDK + Lerna + Webpack combined gives you a powerful set of tools to cleanly manage infrastructure and application logic. As we’ve seen here, this combo provides a particularly elegant approach to building serverless APIs hosted on AWS. Keep this in mind the next time you need to bang out a service for a proof of concept; you can work quickly while also building a solid foundation to scale your POC if it takes off.