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)