Module "express" not found when trying to deploy Express application

Hi,

I have a monorepo setup for my application and was trying to deploy my Express API server to production but no luck getting it running from Fly instance :frowning:

I am able to complete Docker build step and deploying to Fly instance but itā€™s throwing me an error when trying to start the server stating Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'express' imported from /app/src/server.js. I did a ls node_modules in my start command and found all the modules are actually there in the server.

I setup my monorepo using pnpm workspace and turborepo. My monorepo folder structure is as below:

>apps
  > server
    > api // All my api code is here
        > server.js // Classic express server setup
        > package.json // Package.json for my api server
> packages
  > //... Internal packages
package.json // Package.json shared in monorepo
.pnpm-lock.yaml // Lock file for monorepo, only have one
fly.toml
Dockerfile
.dockerignore

As you can see, I need to put fly.toml , Dockerfile and .dockerignore in root as we have shared packages that need to be resolved by pnpm.

I have configure the default fly.toml, Dockerfile to match my monorepo from the default generated template as the following:

fly.toml

# fly.toml app configuration file generated for casa-mono-api on 2023-05-07T18:41:20+08:00
#
# See https://fly.io/docs/reference/configuration/ for information about how to use this file.
#

app = "my-api"
primary_region = "sin"

[env]
  PORT = "6060"

[http_service]
  internal_port = 6060
  force_https = true
  auto_stop_machines = true
  auto_start_machines = true

Dockerfile

# syntax = docker/dockerfile:1

# Adjust NODE_VERSION as desired
ARG NODE_VERSION=16.18.1
FROM node:${NODE_VERSION}-slim as base

LABEL fly_launch_runtime="NodeJS"

# NodeJS app lives here
WORKDIR /app

RUN npm install -g pnpm

# Set production environment
ENV NODE_ENV=production

# Throw-away build stage to reduce size of final image
FROM base as build

# Install packages needed to build node modules
RUN apt-get update -qq && \
    apt-get install -y python pkg-config build-essential 

COPY --link . .

# Install node modules
RUN pnpm i --filter api

# Final stage for app image
FROM base

# Copy built application
COPY --from=build /app/apps/server/api /app

# Start the server by default, this can be overwritten at runtime
CMD [ "pnpm", "run", "start" ]

package.json (of apps/server/api)

{
  "name": "api",
  "version": "2.0.0",
  "main": "index.js",
  "type": "module",
  "scripts": {
    "start": "ls node_modules && node src/server.js",
    "dev": "nodemon src/server.js",
    "init": "node ./scripts/init.js"
  },
  "author": "me",
  "license": "ISC",
  "devDependencies": {
    "eslint-config-standard": "^17.0.0",
    "eslint-plugin-import": "^2.22.1",
    "eslint-plugin-node": "^11.1.0",
    "eslint-plugin-promise": "^6.1.1",
    "nodemon": "2.0.22"
  },
  "dependencies": {
    "axios": "^1.3.5",
    "bcryptjs": "2.4.3",
    "body-parser": "1.20.2",
    "dayjs": "1.11.7",
    "express": "4.18.1",
    "express-http-context": "^1.2.4",
    "jsonwebtoken": "9.0.0",
    "mailgun-js": "0.22.0",
    "mongoose": "7.0.3",
    "mongoose-long": "0.6.0",
    "morgan": "1.10.0",
    "passport": "0.6.0",
    "passport-jwt": "4.0.1",
    "uuid": "9.0.0",
    "winston": "3.8.2",
    "constant-utils": "workspace:*",
    "helper-utils": "workspace:*"
  },
  "lint-staged": {
    "*.js": "prettier --write"
  }
}

Iā€™m not sure what I did wrong in any of the steps as I am new to using Docker and Fly and there is limited amount of doc for monorepo Express API setup for fly.io. Appreciate if anyone can help.

Iā€™m not (yet!) familiar enough with pnpmā€™s inner workings, but I suspect that there is a full path someplace internally when dealing with monoreps.

It looks like you are only deploying one app. If so, perhaps instead of:

COPY --link . .
RUN pnpm i --filter api
...
COPY --from=build /app/apps/server/api /app

You could try:

COPY --link apps/server/api .
RUN pnpm i
...
COPY --from=build /app /app

Another approach is to copy the whole tree and then change the workdir. So replace:

COPY --from=build /app/apps/server/api /app

with

COPY --from=build /app /app
WORKDIR /app/apps/server/api
1 Like

Hey,

Thanks for your reply! The below works:

Apparently pnpm install everything in root node_modules/.pnpm and create a symbolic link to the module from workspace node_modules.

This wouldnā€™t work as I tried before. It needs to have access to root folder because .pnpm-lock is in there

Iā€™m glad you are working, and Iā€™ve learned something in the process. Thank you! Iā€™ve opened Better support for monorepos to track the changes needed to better support your use case.

My goal is to make the dockerfile we generate either ā€œjust workā€, and when that is not possible allow customization by rerunning npx dockerfile with flags. Feel free to take a look at the dockerfile-node repository, open issues and (if you are so inclined) pull requests. The code is pretty straightforward, mostly a single class, yargs, and ejs templates.

1 Like

Hey @rubys ,

May I know whatā€™s the right way to incorporate process manager like PM2 for node to manage our node.js process? It includes stuff like auto-restart, good enough dashboard, and also concurrent mode for application (if we have >1 CPU in the instance)