Getting type error with changing syntax from require to import

I got an error when I change the syntax from require, CommonJS, to import, ES Module.

I tried to create a todo-app with Node.js, TypeScript, MySQL.

First, I wrote the following code.

// db.ts
export {};
const mysql = require('mysql2');

const pool = mysql.createPool({
  host: 'localhost',
  user: 'user',
  password: 'password',
  database: 'db',
});

module.exports = pool;
//index.ts
import express from 'express';
import { Express, Request, Response } from 'express';
import { QueryError, RowDataPacket } from 'mysql2';
const pool = require('./db');

const app: Express = express();

app.get("/todos", async (req: Request, res: Response) => {
  await pool.promise().query(
    'SELECT * FROM todo'
  )
  .then((rows: RowDataPacket[]) => {
    res.json(rows[0]);
  })
  .catch((error: QueryError) => {
    throw error;
  })
});

These codes work well. There is no error. However, using import instead of require brings me an error.

//db.ts
import { createPool } from 'mysql2';

export const pool = createPool({
  host: 'localhost',
  user: 'user',
  password: 'password',
  database: 'db',
});
//index.ts
import express from 'express';
import { Express, Request, Response } from 'express';
import { QueryError, RowDataPacket } from 'mysql2';
import { pool } from '.db';

const app: Express = express();

app.get("/todos", async (req: Request, res: Response) => {
  await pool.promise().query(
    'SELECT * FROM todo'
  )
  .then((rows: RowDataPacket[]) => {
    res.json(rows[0]);
  })
  .catch((error: QueryError) => {
    throw error;
  })
});

I got a following error.

Argument of type '(rows: RowDataPacket[]) => void' is not assignable to parameter of type '(value: [RowDataPacket[] | RowDataPacket[][] | OkPacket | OkPacket[] | ResultSetHeader, FieldPacket[]]) => void | PromiseLike<...>'.
  Types of parameters 'rows' and 'value' are incompatible.
    Type '[RowDataPacket[] | RowDataPacket[][] | OkPacket | OkPacket[] | ResultSetHeader, FieldPacket[]]' is not assignable to type 'RowDataPacket[]'.
      Type 'RowDataPacket[] | RowDataPacket[][] | OkPacket | OkPacket[] | ResultSetHeader | FieldPacket[]' is not assignable to type 'RowDataPacket'.
        Type 'RowDataPacket[]' is not assignable to type 'RowDataPacket'.
          The types of 'constructor.name' are incompatible between these types.
            Type 'string' is not assignable to type '"RowDataPacket"'.

Why I had this error? I think using require or import gives same functionality, so I’m very confused.


extra info

tsconfig.json
{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "skipLibCheck": true
  }
}

Answer

Simply specifying the exact type of the result set you’re getting helps (.query(...) to .query<RowDataPacket[]>(...)).

(I also changed your mixing-and-matching of async with .then to an await.)

app.get("/todos", async (req: Request, res: Response) => {
  const resp = await pool.promise().query<RowDataPacket[]>(
    'SELECT * FROM todo'
  );
  res.json(resp[0]);
});

I’d guess the types for mysql2 were quite loose in the require case, so you were never truly validating them.

For completeness’ sake, this is the full single-file test program that successfully typechecks.

import express from 'express';
import { Express, Request, Response } from 'express';
import { RowDataPacket, createPool } from 'mysql2';

const pool = createPool({
  host: 'localhost',
  user: 'user',
  password: 'password',
  database: 'db',
});

const app: Express = express();

app.get("/todos", async (req: Request, res: Response) => {
  const resp = await pool.promise().query<RowDataPacket[]>(
    'SELECT * FROM todo'
  );
  res.json(resp[0]);
});