next.jsbuild-errorapp-routersequelize-typescript

nextJS not building, throwing 'TypeError: Invalid URL'


No matter what I try my nextjs 14 app keeps failing on build (on this for days now). Unfortunately the error is thrown in webpack'd minimized files, but monkey patching and commenting out has me thinking its the sequelize-typescript implemention.

In essence I'm trying to use nextJS for just an api, hence I'm using the App Router.

npm run dev and npm run test work without issue. Its just when npm run build is called that things blow up. This makes me think that I am missunderstanding the app router in nextjs and its an architecture issue with my method of implementing sequelize-typescript?

If I touch the db anywhere, then the error is thrown on npm run build, eg: In the router app/api/router.ts

export async function POST(...){

  // will throw Invalid URL error
  await db.sequelize.sync()
  await db.sequelize.authenticate()

  // import from lib/binance.ts also throw if above is commented
  await binanceLib.validate()
}

I've recreated the error here: https://github.com/daithi-coombes/temp-nextjs-sequelize-invalid-url-error

Error:

...
 ✓ Compiled successfully
 ✓ Linting and checking validity of types    
   Collecting page data  ...TypeError: Invalid URL
    at new URL (node:internal/url:775:36)
    at 84334 (.next/server/app/api/buscuit-machine/route.js:1:6500)
    at o (.next/server/webpack-runtime.js:1:143)
    at 83773 (.next/server/app/api/buscuit-machine/route.js:2:357)
    at o (.next/server/webpack-runtime.js:1:143)
    at 77123 (.next/server/app/api/buscuit-machine/route.js:2:1898)
    at o (.next/server/webpack-runtime.js:1:143)
    at 37218 (.next/server/app/api/buscuit-machine/route.js:1:1908)
    at o (.next/server/webpack-runtime.js:1:143)
    at o (.next/server/app/api/buscuit-machine/route.js:37:781295) {
  code: 'ERR_INVALID_URL',
  input: 'undefined'
}

Tree structure:

.
├── app
│   └── api
│       ├── my-route-1
│       │   └── route.ts
│       ├── my-route-2
│       │   └── route.ts
├── config
│   └── config.ts
├── lib
│   ├── binance.ts
│   ├── db.ts
│   ├── providers.ts
├── models
│   ├── connection.ts
│   ├── index.ts
│   ├── order.ts
├── next.config.js
├── next-env.d.ts
└── tsconfig.json

The code flow is:

The Code:

app/api/my-route/route.ts

...

// @see https://stackoverflow.com/a/76401805/288644
export const dynamic = 'force-dynamic'
import { loadLibraries } from '../../../lib/providers'

export async function POST(req: NextRequest) {

  const { binance, telegram } = await loadLibraries()

  const data = await req.json()
  // await db.sequelize.sync()
  // await db.sequelize.authenticate()
  trade = await DB.newTrade(await binance.validate(data))
  ...

If I comment out Order.update() below, then all will build with no problem, uncomment and the error above is thrown.

lib/binance.ts

import Order from '../models/order'

class Binance{
  async validate(trade:Trade):Promise<OrderI>{
    ...

    // try{
    // await Order.update({ ... }, {
    //   where: {
    //     id: trade.orderId,
    //   },
    // })
    // }catch(e) {
    //   console.log('e: ', e)
    //   throw e      
    // }

    return order
  }

models/connection.ts

const env:string = process.env.NODE_ENV || 'development'
const config:SequelizeOptions = Config[env]
let sequelize:Sequelize

config.host = config.host ? config.host : '127.0.0.1' // a hack to try remove build 'Invalid URL' error. Error still persists.

config.dialect = 'postgres'
config.dialectModule = pg

sequelize = new Sequelize(config)

export default sequelize

models/order.ts

@Table({
  tableName: 'Orders'
})

class Order extends Model<OrderI> {
  @Column({
    type: DataType.INTEGER,
    primaryKey: true,
    unique: true,
    field: 'id'
  })
  @ForeignKey(() => Strategy)
  id: number

  ...
}

export default Order

models/index.ts

...
const db = {
  sequelize,
  Sequelize,
  Strategy: strategy,
  Order: order,
}

fs
  .readdirSync(modelPath)
  .filter(file => {
    return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js')
  })
  .forEach(file => {
    const model = require(__dirname + '/../models/' + file)(sequelize, Sequelize.DataTypes)
    db[model.name] = model
  })

Object.keys(db).forEach(modelName => {
  if (db[modelName].associate) {
    db[modelName].associate(db)
  }
})

db.sequelize = sequelize
db.Sequelize = Sequelize

export default db

any help appreciated

Edit:

next.config.js

/**
 * @type {import('next').NextConfig}
 */
module.exports = {
  async headers() {
      return [
      {
        source: '/(.*)',
        headers: [
          {
            key: 'Access-Control-Allow-Origin',
            value: 'http://localhost:3000',
          },
        ],
      },
    ]
  },
  experimental: {
    webpackBuildWorker: true,
    serverComponentsExternalPackages: ['sequelize', 'sequelize-typescript'],
  },
}

env.local

API_URL=http://localhost:3000
NEXT_PUBLIC_APP_URL=http://localhost:3000
BINANCE_API_KEY=
BINANCE_API_SECRET=
BINANCE_ENDPOINT=https://testnet.binancefuture.com
DB_HOST=127.0.0.1
DB_USER=postgres
DB_PASSWORD=mysecretpassword
DB_NAME=foobar
TELEGRAM_BOT_TOKEN=

Versions:


Solution

  • The problem lies in the minified version of your code. Take a look at the output of your api route .next/server/app/api/buscuit-machine/route.js. It contains lots of hard to read minified code but this line is where the problem lies:

    let e = new URL(process.env.DATABASE_URL);
    

    Now this code is not an inherent problem. It simply assigns variable e to a url. Specifically the DATABASE URL. This is where the problem is; process.env.DATABASE_URL is undefined. undefined is not a valid url. So now this code simplifies to the following:

    let e = new URL(undefined);

    This is a bug with Sequlize I would recommend opening a new issue. However, there is a simple fix. Add the following to your .env

    DATABASE_URL=postgres://user:pass@example.com:5432/dbname