TypeScript / Node.js: Importing  Knex into an ES6 module
Tags: typescript, node
Published: 2021-04-05 17:02:13

A while back I wrote a file based import in TypeScript as a Node.js cli app. I used Knex for it, in a rather simple way, starting out from this code: 
```typescript
import Knex from 'knex'
import { Dict } from './utils.js'

let _knex: Knex;
export function connect(connection: Dict<string>) {
    let conn = {
        client: 'pg',
        connection
    }
    _knex = Knex(conn as Knex.Config);
}
```
It just worked, and I didn't think much into why it did, at the time. 

I should add here that for Node.js projects I've tried to move over to using ES6 modules in my server side code (away from CommonJS). That can cause challenges, at times.

Yesterday, I wanted to do something similar, so I started a new project. A new _package.json_, and new _tsconfig.json_ and I copied and pasted the code below. **It no longer worked!**

After a while, I found out that **knex.js** was resolved to version *0.21.18* in my original project and to *0.95.4* in my new project (by means of *package.json*). 

Reconfiguring my new project back to CommonJS, I got it working, via this line in _tsconfig.json_: 
```json
    "module": "CommonJS", // It was 'ESNext' before
```
...and the corresponding in _package.json_ (I removed _'type': 'module'_).

But I did not want to run my code on the server in CommonJS mode! 

I felt the frustration of simply copying code and settings that has worked well before and sitting there with errors in my terminal... What had changed? 

### Different versions of Knex.js 
So there was this significant jump, from v0.21.18 to v0.95.4. The issue must be there, somewhere. 

I opened my two projects next to each other and popped up IDE type hints for the same imported Knex object. This is how it looked in the old project: 
![The imported object has three type aliases](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0mc7evkub29vej916gbw.png)
 
While this is how it looked (very similar code) in the new project: 
![Here it has only two!](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vb11dkj0asqh9mfn7yp0.png)

If you look closely, you see that the first image contains a type alias for the Knex **interface** - this is missing in the second picture. In both cases, the code (behind the type hints) is: 
```typescript 
import Knex from 'knex'; 
```
In the first case, the symbol *Knex* is apparently **both** the interface type of the Knex package **and** the function one can invoke, to connect with the database (the default export in CommonJS). 

In the second case, the type information is not there anymore in the default import - it is just a function (with a signature). (If you look at my initial code segment you see that the exact identifier **Knex** is used in two quite different ways). 

That was the difference. 

How TypeScript gets away with using the same identifier as 
- A type (the Knex interface)
- A function to be called 

... I don't understand. But that was what dropped away between the earlier and later version of Knex. 

### Solution 1
So my change then was to name another import (to get both the function and the interface):
```typescript
import { knex, Knex } from 'knex';
```
Then my new code actually builds and runs... but only in CommonJS mode. Built as an ES6 module, I get this on launching it: 
```bash
$ node lib/cmd.js
file:///home/arst/src/mifl/lib/cmd.js:4
import { knex } from 'knex';
         ^^^^
SyntaxError: Named export 'knex' not found. The requested module 'knex' is a CommonJS module...
```
At this point... it felt like I had exhausted my ways forward. However, I remembered that the code originally was just one single default import. What about keeping that one, **and** on top of that, doing a named import of the TS interface?  

### Solution 2
This then was my new attempt: 
```typescript
import knex, { Knex } from 'knex';
let knex_conn: Knex;
async function connect(connection: Record<string, string>) {
    let conn = {
        client: 'pg',
        connection
    }
    knex_conn = knex(conn);
    return knex_conn;
}
```
And that turns out to work just fine, both when the code is built and run as CommonJS and as an ES module. 

The key point is that the interface type and the function are two different things. And... to get to the actually exported object (from Knex.js), we have to use a default import from an ES module.

### Summary
It took me a few hours to experiment my way here, and I did not really find very good TypeScript examples using this combination of both default and named imports - particularly when used from an ES module, neither in Knex documentation nor anywhere else. 

What I did find (in issues for Knex on GitHub) was that people had issues running imports in TypeScript, and that some solved it by converting their code into CommonJS. 

Knowing that things had worked just fine from an ES module for me, a year before, made me want to dig deeper. Thus this post. 

I would guess the same pattern applies to many other primarily *CommonJS* based packages that one want to import with TypeScript bindings.