serverless-webpack: Type Error - Path must be a string

This is a Bug Report

Description

$ serverless package

  Type Error ---------------------------------------------

  Path must be a string. Received { index: './index.js', data: './data.js' }

Additional Data

$ cat package.json | tail
    "cli"
  ],
  "author": "Adrian Sieber",
  "license": "ISC",
  "devDependencies": {
    "serverless": "^1.21.0",
    "serverless-webpack": "^2.2.2",
    "webpack": "^3.5.5"
  }
}
$ neofetch
                -/+:.           adrian@adrians-mac.local
               :++++.           ------------------------
              /+++/.            OS: macOS Sierra 10.12.6 16G29 x86_64
      .:-::- .+/:-``.::-        Model: MacBookPro12,1
   .:/++++++/::::/++++++/:`     Kernel: 16.7.0
 .:///////////////////////:`    Uptime: 1 day, 10 hours, 8 mins
 ////////////////////////`      Packages: 930
-+++++++++++++++++++++++`       Shell: fish 2.6.0
/++++++++++++++++++++++/        Resolution: 2560x1440, 1680x1050, 1920x1080
/sssssssssssssssssssssss.       DE: Aqua
:ssssssssssssssssssssssss-      WM: Quartz Compositor
 osssssssssssssssssssssssso/`   WM Theme: Blue
 `syyyyyyyyyyyyyyyyyyyyyyyy+`   Terminal: iTerm2
  `ossssssssssssssssssssss/     CPU: Intel i5-5257U (4) @ 2.70GHz
    :ooooooooooooooooooo+.      GPU: Intel Iris Graphics 6100
     `:+oo+/:-..-:/+o+/-        Memory: 4385MiB / 8192MiB

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 3
  • Comments: 32 (13 by maintainers)

Most upvoted comments

Hi @jnreynoso! What I see you are trying to do here is dynamic module loading, and with webpack you should avoid it as much as you can. If you are using serverless framework, your code should be atomic and service-based, because all those services (and AWS is the best example for this) have a bad cool startup times, and if you overcharge your services or do a monolyth, it can be a disaster in this aspect.

Javascript and node.js is going to static module loading, because of ES2015 imports and its implementation in node. Said that, webpack will appreciate you to use static moudle loading, and Models are one thing you should require as well.

Let’s say you have two modules with association (sorry for ES2015 imports, you can replace they with explicit requires and export default by module.exports =): /app/models/message.js:

import { DataTypes } from 'sequelize';
import sequelize from '../../utilities/database/mysql';


const Message = sequelize.define('Message', {
  title: DataTypes.STRING,
});

export default Message;

/app/models/question.js:

import { DataTypes } from 'sequelize';
import sequelize from '../utilities/database/mysql';
import Message from './message';


const Question = sequelize.define('Question', {
  title: DataTypes.STRING,
});

Question.hasMany(Message);
Message.belongsTo(Question);

export default Question;

As you can see, we avoid ciclic dependencies by adding all associations at the same file question.js. I’ll explain you how to require them if you need those asociations but you only use the Message model. Also there’s a require from sequelize. This import it’s only an export of a sequelize instance with all the details to the database connection.

after that, we have the handlers for each function: /app/handlers/add-question.js:

import Question from '../models/question';

module.exports.addQuestion =
  ({ body }, context, callback) => {
    context.callbackWaitsForEmptyEventLoop = false;
    Question.create(body)
      .then(question => callback(null, {
        body: JSON.stringify(question),
      }));

/app/handlers/add-message.js:

import Message from '../models/message';
import '../models/question';

module.exports.addQuestion =
  ({ body, pathParameters }, context, callback) => {
    context.callbackWaitsForEmptyEventLoop = false;
    body.QuestionId = pathParameters.id;
    return Message.create(body)
      .then(message => callback(null, {
        body: JSON.stringify(message),
      }));
  };

as you can see here, we import question to get the associations with Question and Message. As we are statically importing, we can’t import Question inside Message module because we will end up with a ciclic import, and our program will fail. So that’s the best way to solve this problem.

and finally, the serverless.yml functions extract:

addQuestion:
  handler: app/handlers/add-question.addQuestion
  timeout: 10
  events:
    - http:
        path: questions
        method: post
        cors: true
addMessage:
  handler: app/handlers/add-message.addMessage
  timeout: 12
  events:
    - http:
        path: messages
        method: post
        cors: true

As you can see, we require each model, and only if we need it. If we do that, webpack will know how to pack your function and you will have at the end all the modules required inside your packed function. If you pack individually, you’ll endup with a tiny zip with only the required files, and your project will reduce the cool startup time and have a better impact in performance.

I know it’s a bit frustrating at the begining, but you’ll have a better, clean and mantainable code, and also working models in serverless. Also a good thing to keep in mind is the context.callbackWaitsForEmptyEventLoop = false; because without that, you can have a lot of problems and timeouts.

I haven’t tested the code, so it’s only a reference. If you need further assistance I’ll be pleased to help you. Also, if you can’t code that way, or you have other requirements, I can help you with those specific requirements. This is the way I do, but not the only way. As HyperBrain has said, you can include all the modules in webpack at the begining of your packaging and have all the models inside, but it’s not a good idea in terms of mantainability and stability, I think.

Hope this helps! Cheers.

@hyperbrain, @jnreynoso, I can post a working example of what I’ve built tomorrow or Wednesday. Currently a little OOC.

Hello @mizhac . It’s not necessary to close the connection if you specify context.callbackWaitsForEmptyEventLoop = false; at the begining of your function. This makes serverless not to wait until every event loop is finished, and it will be leaved opened until the aws system reuses the node process (and you will have faster invokation times) or drops the node process. There’s no specific reuse time, but it’s useful when you have concurrent api calls.

To take advantage to this, you should always declare your connections outside the function, like this:

import Sequelize from 'sequelize';
const sequelize = new Sequelize(/*your connection details*/);

module.exports.addQuestion =
  ({ body, pathParameters }, context, callback) => {
    context.callbackWaitsForEmptyEventLoop = false;
    // do your sequelize code here
    PromiseFulfilled.then(() => callback(null, { body: "{}" }));
  };

many thanks @jpicornell , I already have it clear, implement my project following the guidelines you gave and everything is working well!