Can not import modules from Lambda Layers with Serverless framework and TypeScript - node.js

I have several functions in my serverless app. Two of them are for REST endpoints and one is SQS handler. They all are using the same libraries. So, I want to move them to Lambda Layer and share across functions to reduce size.
I'm using Serverless framework 2.46, TypeScript 4.3 and NodeJS 14.
I have the following project structure:
/
- layers/
- nodejs/
- node_modules/
- package.json
- src/
- handlers/ - here are my handlers
- etc...
I've configured TypeScript to import libraries from the layer folder like this import middy from '/opt/nodejs/#middy/core';. Here is my tsconfig
{
"compilerOptions": {
"preserveConstEnums": true,
"strictNullChecks": true,
"sourceMap": true,
"allowJs": false,
"target": "ES2020",
"module": "CommonJS",
"outDir": ".build",
"moduleResolution": "node",
"esModuleInterop": true,
"resolveJsonModule": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"lib": [
"ES6",
"ES2019",
"ES2020"
],
"baseUrl": ".",
"paths": {
"/opt/nodejs/*": [
"layers/nodejs/node_modules/*"
]
}
},
"exclude": [
"node_modules",
"opt/nodejs/node_modules"
]
}
And have serverless config like this
service: my_serverless-app
frameworkVersion: '2'
useDotenv: true
variablesResolutionMode: 20210326
configValidationMode: error
custom:
stage: ${opt:stage, self:provider.stage}
dbHost:
local: ${env:DB_HOST, ''}
dev: ${ssm:DB_HOST_DEV}
dbPort:
local: ${env:DB_PORT, ''}
dev: ${ssm:DB_PORT_DEV}
dbUser:
local: ${env:DB_USER, ''}
dev: ${ssm:DB_USER_DEV}
dbPassword:
local: ${env:DB_PASSWORD, ''}
dev: ${ssm:DB_PASSWORD_DEV}
dbName:
local: ${env:DB_NAME, ''}
dev: ${ssm:DB_NAME_DEV}
provider:
name: aws
region: us-east-1
stage: dev
runtime: nodejs14.x
lambdaHashingVersion: 20201221
environment:
NODE_PATH: "./:opt/nodejs/node_modules"
DB_HOST: ${self:custom.dbHost.${self:custom.stage}}
DB_PORT: ${self:custom.dbPort.${self:custom.stage}}
DB_USER: ${self:custom.dbUser.${self:custom.stage}}
DB_PASSWORD: ${self:custom.dbPassword.${self:custom.stage}}
DB_NAME: ${self:custom.dbName.${self:custom.stage}}
plugins:
- serverless-plugin-typescript
- serverless-offline
functions:
getLedgerRecords:
handler: src/handlers/ledger.ledgerRecords
events:
- http:
path: /ledger-records
method: get
layers:
- { Ref: CommonLibsLambdaLayer }
getLedgerRecord:
handler: src/handlers/ledger.ledgerRecord
events:
- http:
path: /ledger-records/{id}
method: get
layers:
- { Ref: CommonLibsLambdaLayer }
layers:
CommonLibs:
path: layers/nodejs
description: "Common dependencies"
compatibleRuntimes:
- nodejs14.x
When I run the app locally via command serverless offline --stage local I have no error, but when I execute an REST endpoint (or any other) I have the following error:
[offline] Loading handler... (D:\Projects\services\.build\src\handlers\ledger)
[offline] _____ HANDLER RESOLVED _____
offline: Failure: Cannot find module '/opt/nodejs/#middy/core'
Require stack:
- D:\Projects\services\.build\src\handlers\ledger.js
- D:\Projects\services\node_modules\serverless-offline\dist\lambda\handler-runner\in-process-runner\InProcessRunner.js
- D:\Projects\services\node_modules\serverless-offline\dist\lambda\handler-runner\in-process-runner\index.js
- D:\Projects\services\node_modules\serverless-offline\dist\lambda\handler-runner\HandlerRunner.js
- D:\Projects\services\node_modules\serverless-offline\dist\lambda\handler-runner\index.js
- D:\Projects\services\node_modules\serverless-offline\dist\lambda\LambdaFunction.js
- D:\Projects\services\node_modules\serverless-offline\dist\lambda\LambdaFunctionPool.js
- D:\Projects\services\node_modules\serverless-offline\dist\lambda\Lambda.js
- D:\Projects\services\node_modules\serverless-offline\dist\lambda\index.js
- D:\Projects\services\node_modules\serverless-offline\dist\ServerlessOffline.js
- D:\Projects\services\node_modules\serverless-offline\dist\index.js
- D:\Projects\services\node_modules\serverless-offline\dist\main.js
- D:\Projects\services\node_modules\serverless\lib\classes\PluginManager.js
- D:\Projects\services\node_modules\serverless\lib\Serverless.js
- D:\Projects\services\node_modules\serverless\scripts\serverless.js
- D:\Projects\services\node_modules\serverless\bin\serverless.js
Also, I have the same problem when I'm trying to deploy the app.
What am I doing wrong? Please drop me a link for tutorial how to configure lambda layers properly. Thanks in advance!

your layer configuration is correct from the Serverless Framework and TypeScript perspective.
the problem could be in the packing of the project itself (e.g. internal of serverless-plugin-typescript)
i would suggest trying another TypeScript plugin, like serverless-esbuild
using your tsconfig.json example and samples from serverless.yml. I created an example here:
https://github.com/oieduardorabelo/2021-07-21-serverless-typescript-layers
it is using esbuild for packing and transpile TypeScript to JavaScript and it is working as expected

Related

Custom Dashboard for AdminJS not working in production

I have a Koa nodejs server which I added AdminJS to and it's working beautifully locally. My goal is to override the Dashboard component. I did so successfully when not running in production. However when I run in production mode (NODE_ENV=production node ./dist/server.js) it fails silently.
const componentLoader = new ComponentLoader();
const Components = {
Dashboard: componentLoader.add("Dashboard", "./admin/dashboard"),
};
const admin = new AdminJS({
componentLoader,
dashboard: {
component: Components.Dashboard,
}
});
My dashboard.tsx file is in src/admin/ and admin is a folder on the same level as src/server.ts. Also, my componentLoader when I inspect it is showing the correct filePath that ends with dist/admin/dashboard
Also, when I check dist/admin/dashboard.js I see my React code. So my tsconfig seems to be correct and the dashboard.tsx has a default export.
What confuses me is when I run nodemon --watch src --exec node -r esbuild-register src/server.ts is works correctly so it seems in general I have things hooked up correctly.
Lastly, here's my tsconfig.json.
{
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
"jsx": "react",
"lib": [
"es6"
],
"target": "es2017",
"module": "commonjs",
"esModuleInterop": true,
"resolveJsonModule": true,
"strict": true,
"allowSyntheticDefaultImports": true,
"noImplicitAny": true,
"allowJs": false,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"noImplicitReturns": true,
"strictNullChecks": true,
"moduleResolution": "node",
"inlineSources": true,
"sourceRoot": "/",
"sourceMap": true,
"isolatedModules": true,
"outDir": "./dist",
"rootDir": "./src",
"composite": true,
"baseUrl": ".",
"paths": {
"src/*": [
"src/*"
]
}
},
"exclude": [
"node_modules",
"./node_modules/*"
],
"files": [
"./src/server.ts"
],
"include": [
"./src/**/*",
"./src/*"
]
}
UPDATE:
I did notice that the components.bundle.js file was missing when navigating to my adminjs dashboard. Since I am using GCP App Engine, I know that that file will not able to be built and saved on the fly in the file system so I have integrated #adminjs/bundler which creates the missing files. However the piece I still haven't put together is how to integrate it into the build pipeline (in particular I'm not sure what the destination of the components.bundle.js should be).
Before I explain my solution here are a few pieces of context:
When using NODE_ENV=production, adminjs does a few things differently, in particular the components.bundle.js file gets served differently. In production, it looks for the file at ./.adminjs/bundle.js
That's when the bundler comes in (which is necessary anyway for certain cloud environments like GCP App Engine). You have to create your own components.bundler.js file which they have a tool for.
First, I created a file which bundles the frontend components. I have not tried doing that with the ComponentLoader so I wouldn't need duplicate code yet, but here's what I know for certain works:
import AdminJS, { OverridableComponent } from "adminjs";
const bundle = (path: string, componentName: string) =>
AdminJS.bundle(`./${path}`, componentName as OverridableComponent);
export const DashboardComponent = bundle("../src/dashboard", "Dashboard");
I believe if I were to create a file which creates the ComponentLoader and adds the components that it would be equivalent (it would export the Components and the componentLoader for use by the AdminJS configuration).
Note ../src/dashboard is simply the location of the dashboard.tsx file I chose. And Dashboard is the name of the component.
Then, I created a script which uses #adminjs/bundler to actually create the bundles. (I named it bundler.ts).
import { bundle } from "#adminjs/bundler";
/**
* yarn admin:bundle invokes this script.
* This file is used to bundle AdminJS files. It is used at compile time
* to generate the frontend component bundles that are used in AdminJS.
*/
void (async () => {
await bundle({
customComponentsInitializationFilePath: "./components.ts",
destinationDir: "./.adminjs",
});
})();
I added a script to my package.json which does the following:
ts-node ./bundler.ts && mv ./.adminjs/components.bundle.js ./.adminjs/bundle.js
Now, when I run this script (which I do when I run before doing node ./dist/server.js), the adminjs router is going to be able to find the previously missing file.
Note that when running your server you'll also want to make sure you set ADMIN_JS_SKIP_BUNDLE='true'.
I hope this helps the next person. I also do hope some documentation and better tooling is on its way. This is kind of messy but solved my issue for now.

TypeScript on AWS Lambda: to bundle imports (how?) or not to bundle? or: Runtime.ImportModuleError: Cannot find module '#aws-sdk/..."

I have the following lambda.ts code I'm trying to make running on an AWS Lambda:
import 'aws-sdk'
import { /* bunch of stuff... */ } from "#aws-sdk/client-cloudwatch-logs";
import {Context, APIGatewayProxyResult} from 'aws-lambda';
import {DateTime} from "luxon";
export const lambdaHandler = async (event: any, context: Context): Promise<APIGatewayProxyResult> => {
/* ... stuff ... */
}
which gets transpiled to:
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.lambdaHandler = void 0;
require("aws-sdk");
//import {CloudWatchClient} from "#aws-sdk/client-cloudwatch";
const client_cloudwatch_logs_1 = require("#aws-sdk/client-cloudwatch-logs");
const luxon_1 = require("luxon");
const lambdaHandler = async (event, context) => {
/* ... transpiled stuff ... */
}
When hitting the button, I'm getting this Response:
{
"errorType": "Runtime.ImportModuleError",
"errorMessage": "Error: Cannot find module '#aws-sdk/client-cloudwatch-logs'\nRequire stack:\n- /var/task/lambda.js\n- /var/runtime/index.mjs",
"trace": [
"Runtime.ImportModuleError: Error: Cannot find module '#aws-sdk/client-cloudwatch-logs'",
"Require stack:",
"- /var/task/lambda.js",
"- /var/runtime/index.mjs",
" at _loadUserApp (file:///var/runtime/index.mjs:951:17)",
" at async Object.UserFunction.js.module.exports.load (file:///var/runtime/index.mjs:976:21)",
" at async start (file:///var/runtime/index.mjs:1137:23)",
" at async file:///var/runtime/index.mjs:1143:1"
]
}
I played a lot with tsconfig.json, trying many things from Google / GitHub / SO / Rumors / Astrology / Numerology / Praying (monotheistic, pantheon-dwelling, neither..), but it only made me more confused.
For instance:
Using the tsconfig.json from Building Lambda functions with TypeScript still emits a single transpiled .js file without embedding the imported #aws-sdk/client-cloudwatch-logs module in the emitted output lambda.js file
Installing the AWS Common Runtime (CRT) Dependency
states that I need to npm install #aws-sdk/... (naturally), but doesn't explain anything beyond, which makes me think that maybe I shouldn't bundle them at all, but simply import them (in the assumption that they are pre-defined/loaded in AWS's Lambda's runtime)
Runtime is Node.js 16.x, Handler is lambda.lambdaHandler (emitted file is called lambda.js), and this is my current tsconfig.json:
{
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
"module": "commonjs",
"moduleResolution": "Node",
"target": "ES2022",
"sourceMap": true,
"lib": [
"ES2021"
],
"typeRoots": ["node_modules/#types"],
"outDir": "build",
"baseUrl": "src",
"strict": true,
"forceConsistentCasingInFileNames": true,
"esModuleInterop": true,
"resolveJsonModule": true,
"inlineSources": true,
"rootDir": "src",
"preserveConstEnums": true,
"isolatedModules": true,
"incremental": true,
"importHelpers": true
},
"exclude": [
"node_modules",
"**/*.test.ts"
]
}
So I'm trying to understand:
Do I even need to bundle those imported modules (such as #aws-sdk/client-cloudwatch-logs) at all, or they are already loaded by AWS Lambda's runtime?
If I do need to bundle, then how? do I need to use some bundler or is it just a matter of configuring tsconfig.json properly?
If bundler isn't mandatory, then how do I setup tsconfig.json to emit those 3rd-party modules?
If a bundler is mandatory, then can they all fit (WebPack, Babel, etc..)? or since no frontend (index.html) is involved, then not all of them can fit?
AWS SDK for JavaScript v3 (AKA modular) is not installed globally in the lambda execution context. You are using a v3 module (#aws-sdk/client-cloudwatch-logs) which is why it fails. AWS SDK v2 is installed globally, you are also using it (aws-sdk) so that require works fine.
You should use a bundler like webpack, esbuild, parcel or rollup. If you are using AWS CDK, there is a nodejs function construct that will do the bundling with esbuild for you.
TS will only emit your compiled javascript. If you are depending on javascript found in your node_modules directory, simply include that directory in your deployment package.
Generally, bundlers will take your application entry points (main.js, handler.js, whatever you want really) and recursively resolve all the dependencies, tree-shake any unreachable code then create one file for each entry point that has no other external dependencies. There is a runtime performance cost to this of course but it does simplify things and in a serverless context it isn't usually too impactful.
So, to resolve your error you can take one of two approaches:
Include your node_modules directory in your deployment package. (trivial)
Use a bundler or CDK (more complex)
Note that in either case, you need to be careful about dependencies with native bindings (binaries basically) as the one installed on your dev machine likely isn't supported in the lambda environment.

Jest Typescript with ES Module in node_modules error - Must use import to load ES Module:

I'm trying to write a simple jest test for a 3rd party package were using that only exports an ES module. It's a wrapper around an http server.
Here is a test repo I setup (just run yarn && yarn jest to reproduce): https://github.com/jamesopti/hocuspocus-testing
No matter what config I experiment with, I still get this error when trying to run it:
Must use import to load ES Module: /Users/j/hocuspocus-testing/node_modules/#hocuspocus/server/dist/hocuspocus-server.esm.js
> 1 | import { Server, Hocuspocus } from '#hocuspocus/server'
| ^
2 | import * as request from 'supertest'
3 |
4 | describe('Server (e2e)', () => {
Things I've tried already:
The Jest instructions on ES modules: https://jestjs.io/docs/ecmascript-modules
In Jest configuration using transformIgnorePatterns
transformIgnorePatterns: ['node_modules/(?!#hocuspocus/)']
Using Babel via babel-jest
modifying transform setup in Jest configuration as '^.+\.jsx?$': 'babel-jest', '^.+\.tsx?$': 'ts-jest'
Ran into the error You appear to be using a native ECMAScript module configuration file, which is only supported when running Babel asynchronously.
Using .babel.config.js instead of .babelrc.js
Any ideas what I'm missing here? I thought this would be straightforward
[EDIT 1] - Added tsconfig.json and a working src/index.ts file to the example repo.
So for anyone still hitting this, ESM configuration explained in this section of documentation :
https://kulshekhar.github.io/ts-jest/docs/guides/esm-support
{
// [...]
"jest": {
"extensionsToTreatAsEsm": [".ts"],
"globals": {
"ts-jest": {
"useESM": true
}
}
}
}
JAN 2023: ES2022, TypeScript 4.9.4, jest 29.3.1, ts-jest 29.0.3
This is what worked for me, after 2 hours of frustration.
I used this configuration in jest.config.ts:
import type { JestConfigWithTsJest } from 'ts-jest'
const config: JestConfigWithTsJest = {
extensionsToTreatAsEsm: ['.ts'],
verbose: true,
preset: 'ts-jest/presets/default-esm',
testEnvironment: 'node',
transform: {
'^.+\\.(ts|tsx)?$': ['ts-jest', { useESM: true }]
},
testPathIgnorePatterns: ['./dist']
}
export default config
Change the test script in package.json to:
I use pnpm. Change to npx jest with npm,
or yarn exec with yarn
...
"scripts": {
...
"test": "NODE_OPTIONS=--experimental-vm-modules pnpm exec jest",
...
}
...
tsconfig.json:
{
"compilerOptions": {
"rootDirs": ["src"],
"outDir": "dist",
"lib": ["ES2022"],
"target": "ES2022",
"module": "ES2022",
"composite": true,
"moduleResolution": "node",
"declaration": true,
"declarationMap": true,
"incremental": true,
"esModuleInterop": true,
"types": ["jest", "node", "#types/jest"],
"sourceMap": true
},
"ts-node": {
"esm": true,
"experimentalSpecifierResolution": "node"
},
"include": ["./src/**/*", "./tests/**/*"]
}
See this (rather confusing) documentation for reference:
https://kulshekhar.github.io/ts-jest/docs/guides/esm-support/
You don't have a tsconfig.json file that specifies module. Therefore it uses the default, where it transpiles all your modules to CommonJS syntax, which uses require.
If you actually look at your dist/hocuspocus-server.esm.js, you should see it using require over the ESM import syntax.
I was having the same problem with my svelte app and testing. I ultimately traced it to having a jest.config.js and a jest.config.json in my root folder. It seems that jest does not have automatic config file resolution and was using a default configuration instead of either of my specified configurations.

"Right-hand side of 'instanceof' is not an object" when testing NestJS + TypeORM using Jest

I have a working NestJS server that performs some TypeORM repository.insert() command. However when running the same operation from a Jest test (using #nestjs/testing's Test.createTestingModule(...), the infamous Right-hand side of 'instanceof' is not an object appears.
Looking in more details, it appears that this is due to some dynamic loading occurring in TypeORM's QueryBuilder:
// loading it dynamically because of circular issue
const InsertQueryBuilderCls = require("./InsertQueryBuilder").InsertQueryBuilder;
That line succeeds when running the NestJS server but fails when running the Jest test. More specifically:
in the NestJS server: QueryBuilder require("./InsertQueryBuilder") returns an ES module with a InsertQueryBuilder in it. When setting a breakpoint here, strangely the debugged file appears located at src/query-builder/QueryBuilder.ts (which is non-existent, and should rather be node_modules/typeorm/query-builder/QueryBuilder.js), but this succeeds.
in the Jest test: require("./InsertQueryBuilder") return an empty object, and so InsertQueryBuilder is undefined which not an object indeed. The debugged file is as expected node_modules/typeorm/query-builder/QueryBuilder.js, but this fails.
I looks like it could be related to my Jest configuration, which is:
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
moduleDirectories: ['node_modules', 'src']
}
as my Typescript sources are under a src directory under the project root. Those path issues are also be related to my tsconfig.json, which contains:
{
"compilerOptions": {
"module": "commonjs",
"allowSyntheticDefaultImports": true,
"target": "es2019",
"baseUrl": "./src",
"outDir": "./dist",
"incremental": true,
"lib": [
"es2019"
]
}

Serverless offline - Migration on local dynamoDb is not working

I'm working in a Serverless project and I'm having issues to run locally my application with dynamodb. It is not creating a data table thus not executing correctly the seed. What is wrong with my configurations?
start command: $ sls offline start --stage dev
error message:
Serverless: Bundling with Webpack...
Serverless: Watching for changes...
Dynamodb Local Started, Visit: http://localhost:8000/shell
Resource Not Found Exception ---------------------------
ResourceNotFoundException: Cannot do operations on a non-existent table
at Request.extractError (/home/mauricio/dev/project/covid-favor-api/node_modules/aws-sdk/lib/protocol/json.js:51:27)
at Request.callListeners (/home/mauricio/dev/project/covid-favor-api/node_modules/aws-sdk/lib/sequential_executor.js:106:20)
at Request.emit (/home/mauricio/dev/project/covid-favor-api/node_modules/aws-sdk/lib/sequential_executor.js:78:10)
at Request.emit (/home/mauricio/dev/project/covid-favor-api/node_modules/aws-sdk/lib/request.js:683:14)
at endReadableNT (_stream_readable.js:1183:12)
at processTicksAndRejections (internal/process/task_queues.js:80:21)
For debugging logs, run again after setting the "SLS_DEBUG=*" environment variable.
Get Support --------------------------------------------
Docs: docs.serverless.com
Bugs: github.com/serverless/serverless/issues
Issues: forum.serverless.com
Your Environment Information ---------------------------
Operating System: linux
Node Version: 12.13.0
Framework Version: 1.64.0
Plugin Version: 3.4.0
SDK Version: 2.3.0
Components Core Version: 1.1.2
Components CLI Version: 1.4.0
Serverless file:
org: mauriciocoder
app: covid-favor
# We are using JEST for testing: https://jestjs.io/docs/en/getting-started.html - npm test
service: covid-favor-app-api
# Create an optimized package for our functions
package:
individually: true
# Create our resources with separate CloudFormation templates
resources:
# API Gateway Handler
- ${file(resources/api-gateway-handler.yml)}
# DynamoDb Handler
- ${file(resources/dynamodb-handler.yml)}
plugins:
- serverless-bundle # Package our functions with Webpack
- serverless-dynamodb-local
- serverless-offline
- serverless-dotenv-plugin # Load .env as environment variables
custom:
authorizer:
dev:
prod: aws_iam
dynamodb:
stages: dev
start:
port: 8000 # always se port 8000, otherwise serverless-dynamodb-client will not find
migrate: true # creates tables from serverless config
seed: true # determines which data to onload
seed:
domain:
sources:
- table: userAccount
sources: [./resources/migrations/v0.json]
provider:
name: aws
runtime: nodejs10.x
stage: ${opt:stage, 'dev'}
region: us-east-1
# These environment variables are made available to our functions
# under process.env.
environment:
helpTableName: help
userAccountTableName: userAccount
# 'iamRoleStatements' defines the permission policy for the Lambda function.
# In this case Lambda functions are granted with permissions to access DynamoDB.
iamRoleStatements:
- Effect: Allow
Action:
- dynamodb:DescribeTable
- dynamodb:Query
- dynamodb:Scan
- dynamodb:GetItem
- dynamodb:PutItem
- dynamodb:UpdateItem
- dynamodb:DeleteItem
Resource: "arn:aws:dynamodb:us-east-1:*:*"
# These are the usage plan for throttling
usagePlan:
throttle:
burstLimit: 2
rateLimit: 1
functions: ...
dynamodb-handler file:
userAccount:
Type: AWS::DynamoDB::Table
DeletionPolicy : Retain
Properties:
TableName: userAccount
AttributeDefinitions:
- AttributeName: userId
AttributeType: S
KeySchema:
- AttributeName: userId
KeyType: HASH
ProvisionedThroughput:
ReadCapacityUnits: 1
WriteCapacityUnits: 1
seed file v0.json:
{
"Table": {
"TableName": "userAccount",
"KeySchema": [
{
"AttributeName": "userId",
"KeyType": "S"
}
],
"LocalSecondaryIndexes": [
{
"IndexName": "local_index_1",
"KeySchema": [
{
"AttributeName": "userId",
"KeyType": "HASH"
}
]
}
],
"ProvisionedThroughput": {
"ReadCapacityUnits": 1,
"WriteCapacityUnits": 1
}
}
}
package.json
{
"name": "notes-app-api",
"version": "1.1.0",
"description": "A Node.js starter for the Serverless Framework with async/await and unit test support",
"main": "handler.js",
"scripts": {
"test": "serverless-bundle test"
},
"author": "",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/AnomalyInnovations/serverless-nodejs-starter.git"
},
"devDependencies": {
"aws-sdk": "^2.622.0",
"jest": "^25.1.0",
"serverless-bundle": "^1.2.5",
"serverless-dotenv-plugin": "^2.1.1",
"serverless-offline": "^5.3.3"
},
"dependencies": {
"serverless-dynamodb-client": "0.0.2",
"serverless-dynamodb-local": "^0.2.35",
"stripe": "^8.20.0",
"uuid": "^3.4.0"
}
}
I think you are missing the migration block, where you specify your migration file. put this under your dynamodb: key
migration:
dir: resources/migrations/v0.json
The error message is explaining where your issue is - the dynamodb-handler.yml file is missing the key, Resources. In the serverless-dynamodb-local docs, you can see the redundant resources & Resources keys. So your dynamodb-handler.yml should begin like this:
Resources:
userAccount:
Type: AWS::DynamoDB::Table
Note that all other external resources must begin with the same key, i.e. api-gateway-handler.yml in your example.
Additionally, if you're having difficulty creating the table when starting serverless offline, or you're using a persistent docker dynamodb instead, migrate with the following command:
npx serverless dynamodb migrate

Resources