In the first article 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:

  1. Install Lerna
  2. Move some files around
  3. 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.