[TypeScript] Bundle Express application with Webpack
Tags: typescript, webpack, express, node
Published: 2021-08-18 15:31:48

## Intro
To create a docker image, I wanted to compile the Express application from TypeScript to JavaScript.
But I got an error because the application couldn't find TypeORM files after compiling.

This time, I will try bundling the application files with Webpack.

* [[TypeScript][PostgreSQL][TSyringe][Express]Try TypeORM 2](https://dev.to/masanori_msl/typescript-postgresql-tsyringe-express-try-typeorm-2-3bka)
* [[TypeScript][Express] Try React](https://dev.to/masanori_msl/typescript-express-try-react-1anc)
* [[TypeScript][Express] Try React 2](https://dev.to/masanori_msl/typescript-express-try-react-2-1bb1)

#### Environments
* Node.js ver.16.6.1
* Express ver.4.17.1
* TypeScript ver.4.3.5
* Webpack ver.5.50.0
* webpack-cli ver.4.7.2
* ts-loader ver.9.2.5
* cors ver.2.8.5
* pg ver.8.6.0
* reflect-metadata ver.0.1.13
* TSyringe ver.4.5.0
* TypeoORM ver.0.2.34

## Bundle an Express application
First, I create an Express application to try bundling.

#### index.ts
```ts
import express from 'express';
import cors from 'cors';

const port = 3098;
const app = express();

const allowlist = ['http://localhost:3000', 'http://localhost:3099']
const corsOptionsDelegate: cors.CorsOptionsDelegate<any> = (req, callback) => {
  const corsOptions = (allowlist.indexOf(req.header('Origin')) !== -1)? { origin: true }: { origin: false };
  callback(null, corsOptions);
};
app.use(express.json());
app.use(cors(corsOptionsDelegate));

app.get('/', async (req, res) => {
    res.json({ message: 'hello' });
});
app.listen(port, () => {
    console.log(`Example app listening at http://localhost:${port}`)
});
```

#### webpack.config.js
```js
var path = require('path');
module.exports = {
    mode: 'development',
    entry: {
        'index': './index.ts',
    },
    target: 'node',
    module: {
        rules: [
            {
                test: /\.tsx?$/,
                use: 'ts-loader',
                exclude: /node_modules/
            }
        ]
    },
    resolve: {
        extensions: [ '.tsx', '.ts', '.js' ]
    },
    output: {
        filename: '[name].js',
        path: path.resolve(__dirname, './js'),
    }
};
```

Although I can get bundled file and execute it by "node js/index.js", I get a warning like below.

```
WARNING in ./node_modules/express/lib/view.js 81:13-25
Critical dependency: the request of a dependency is an expression
 @ ./node_modules/express/lib/application.js 22:11-28
 @ ./node_modules/express/lib/express.js 18:12-36
 @ ./node_modules/express/index.js 11:0-41
 @ ./index.ts 42:32-50

1 warning has detailed information that is not shown.
Use 'stats.errorDetails: true' resp. '--stats-error-details' to show it.
```

To avoid the warning, I add "webpack-node-externals".

#### webpack.config.js
```js
const path = require('path');
const nodeExternals = require('webpack-node-externals');

module.exports = {
    mode: 'development',
    entry: {
        'index': './index.ts',
    },
    target: 'node',
    module: {
        rules: [
            {
                test: /\.tsx?$/,
                use: 'ts-loader',
                exclude: /node_modules/
            }
        ]
    },
    resolve: {
        extensions: [ '.tsx', '.ts', '.js' ]
    },
    externals: [nodeExternals()],
    output: {
        filename: '[name].js',
        path: path.resolve(__dirname, './js'),
    }
};
```

Some samples on some web sites write like below instead of using "nodeExternals()".

```js
...
    externals: ['express'],
...
```

Although I can bundle the application, but when I running it, I will get an error.

```
C:\Users\example\OneDrive\Documents\workspace\node-webpack-sample\js\index.js:62
module.exports = express;
                 ^

ReferenceError: express is not defined
    at Object.express (C:\Users\example\OneDrive\Documents\workspace\node-webpack-sample\js\index.js:62:18)
    at __webpack_require__ (C:\Users\example\OneDrive\Documents\workspace\node-webpack-sample\js\index.js:86:42)
    at eval (webpack://webpack-sample/./index.ts?:42:33)
    at Object../index.ts (C:\Users\example\OneDrive\Documents\workspace\node-webpack-sample\js\index.js:40:1)
    at __webpack_require__ (C:\Users\example\OneDrive\Documents\workspace\node-webpack-sample\js\index.js:86:42)
    at C:\Users\example\OneDrive\Documents\workspace\node-webpack-sample\js\index.js:97:37
    at Object.<anonymous> (C:\Users\example\OneDrive\Documents\workspace\node-webpack-sample\js\index.js:99:12)
...
```

* [How to set up an Express.js API using Webpack and TypeScript. - The Andela Way - Medium](https://medium.com/the-andela-way/how-to-set-up-an-express-api-using-webpack-and-typescript-69d18c8c4f52)
* [Creating a Node Express-Webpack App with Dev and Prod Builds - Ben Grunfeld - Medium](https://binyamin.medium.com/creating-a-node-express-webpack-app-with-dev-and-prod-builds-a4962ce51334)

## Using TypeORM
Because I wanted to use TypeORM like the project what I had created last time, I added TypeORM into the sample.

Although I could add it and execute "npx webpack", I got an error on runtime.

```
C:\Users\example\OneDrive\Documents\workspace\bookstore_sample\entities\author.ts:1
import {Entity, PrimaryGeneratedColumn, Column} from "typeorm";
^^^^^^

SyntaxError: Cannot use import statement outside a module
    at Object.compileFunction (node:vm:352:18)
    at wrapSafe (node:internal/modules/cjs/loader:1031:15)
    at Module._compile (node:internal/modules/cjs/loader:1065:27)
    at Object.Module._extensions..js (node:internal/modules/cjs/loader:1153:10)
    at Module.load (node:internal/modules/cjs/loader:981:32)
    at Function.Module._load (node:internal/modules/cjs/loader:822:12)
    at Module.require (node:internal/modules/cjs/loader:1005:19)
    at require (node:internal/modules/cjs/helpers:94:18)
...
```

I think it's because entity classes are loaded dynamically.
So if I change the code to just connecting the Database and avoiding accessing tables with entity classes, the error won't be occurred.

Now I use "ormconfig.json" for configuration.

#### ormconfig.json
```json
{
    "type": "postgres",
    "host": "localhost",
    "port": 5432,
    "username": "postgres",
    "password": "example",
    "database": "book_store",
    "synchronize": false,
    "logging": false, 
    "entities": ["./entities/*.{ts,js}"],
    "migrations": ["./migrations/*.{ts,js}"],
    "cli": {
       "entitiesDir": "entities",
       "migrationsDir": "migrations"
    }
 }
```

From the result, I moved the config data into "createConnection".

#### dataContext.ts (Before)
```ts
import "reflect-metadata";
import { singleton } from "tsyringe";
import { Connection, createConnection } from "typeorm";

@singleton()
export class DataContext {
    private connection: Connection|null = null;
    public async getConnection(): Promise<Connection> {
        if(this.connection != null) {
            return this.connection;
        }
        this.connection = await createConnection();
        return this.connection;
    } 
}
```

#### dataContext.ts (After)
```ts
import "reflect-metadata";
import { singleton } from "tsyringe";
import { Connection, createConnection } from "typeorm";
import { Author } from "../entities/author";
import { Book } from "../entities/book";
import { Genre } from "../entities/genre";

@singleton()
export class DataContext {
    private connection: Connection|null = null;
    public async getConnection(): Promise<Connection> {
        if(this.connection != null) {
            return this.connection;
        }
        this.connection = await createConnection({
            type: "postgres",
            host: "localhost",
            port: 5432,
            username: "postgres",
            password: "example",
            database: "book_store",
            synchronize: false,
            logging: false,
            entities: [Author, Book, Genre]
        });
        return this.connection;
    } 
}
```

For production, maybe I have to change configuration data like "host".

* [How to Use TypeORM with Webpack - Atomic Spin](https://spin.atomicobject.com/2020/12/21/typeorm-webpack/)