NodeJS
AWS Lambda
Import/Export
Native Support
Cloud Computing

NodeJS 14.x - Native AWS Lambda Import/Export Support

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

The real question behind this topic is whether AWS Lambda can run Node.js handlers written with ES module syntax instead of the older CommonJS pattern. For the Node.js 14.x Lambda runtime, the answer was yes, but only when the package and handler files were configured so Lambda and Node treated them as ECMAScript modules.

CommonJS Versus ES Modules in Lambda

Older Lambda examples usually looked like this:

javascript
1exports.handler = async (event) => {
2  return {
3    statusCode: 200,
4    body: 'ok'
5  };
6};

That is CommonJS. With native ES module support, you can write the handler using import and export:

javascript
1import crypto from 'crypto';
2
3export const handler = async () => {
4  return {
5    statusCode: 200,
6    body: JSON.stringify({ id: crypto.randomUUID() })
7  };
8};

The syntax is cleaner for many modern JavaScript projects, but Lambda only accepts it when Node knows the file is an ES module.

How to Enable ES Module Behavior

You have two main ways to tell Node that your Lambda code uses ES modules:

  • use the .mjs file extension
  • keep .js and set "type": "module" in package.json

Here is a minimal package file:

json
1{
2  "name": "lambda-esm-demo",
3  "type": "module"
4}

With that configuration, a file such as index.js may use import and export directly. If you do not set "type": "module", then plain .js files are treated as CommonJS and import syntax will fail.

The Lambda handler setting still points to the file stem and exported symbol, such as index.handler.

Interoperating With CommonJS Dependencies

Native ESM support does not mean every dependency in your project suddenly behaves the same way. Many packages still use CommonJS semantics, and mixed-module projects can get confusing if you convert everything blindly.

In many cases, importing a CommonJS package from an ES module works:

javascript
1import path from 'path';
2
3export const handler = async () => {
4  return {
5    statusCode: 200,
6    body: path.basename('/tmp/report.txt')
7  };
8};

When you need the older require function inside an ES module, Node provides createRequire:

javascript
1import { createRequire } from 'module';
2
3const require = createRequire(import.meta.url);
4const pkg = require('./legacy-commonjs.cjs');
5
6export const handler = async () => {
7  return {
8    statusCode: 200,
9    body: pkg.message
10  };
11};

That gives you a controlled bridge for legacy code while keeping the handler itself in ESM format.

Practical Deployment Advice

If you are migrating an existing Lambda, convert one function at a time and test the packaging path carefully. Native ES module support solves the syntax problem, but it does not remove the need to verify:

  • the handler name
  • the archive structure
  • dependency resolution
  • local test behavior versus deployed behavior

This matters because mixed .cjs, .mjs, and .js files can work perfectly in local tooling while failing in Lambda if the runtime resolves them differently than you expected.

Common Pitfalls

  • Writing import syntax in a .js file without using "type": "module" in package.json.
  • Assuming CommonJS and ESM packages expose identical import shapes.
  • Mixing module systems across helper files without deciding which file extensions and package settings control the project.
  • Forgetting that the Lambda handler still refers to the exported symbol name, even for ES modules.
  • Migrating a whole codebase at once instead of converting and testing one function at a time.

Summary

  • Node.js 14.x Lambda supported native ES modules when the package was configured correctly.
  • Use .mjs files or set "type": "module" to enable import and export.
  • ES module Lambda handlers use named exports such as export const handler = ....
  • Mixed ESM and CommonJS projects can work, but they require deliberate configuration.
  • Native support removes the need for transpilation just to get module syntax, not the need for deployment testing.

Course illustration
Course illustration

All Rights Reserved.