How to use NPM modules in Kotlin/JS - Discord Bot Series (Part 1)
Tags: kotlin, javascript, discord, kotlinjs
Published: 2022-04-12 16:05:14

KotlinJS is an exciting new method of creating javascript projects that combines the benefits of javascripts speed and versatility and kotlins strong typing and conciseness. Plus the ability to easily add testing and even share code across platforms. 

Since KotlinJS transpiles kotlin code to javascript it gives you all the advantages of executing a NodeJS program, while enjoying the benefits of the kotlin language.

![](https://i.redd.it/nj02sbmhlq861.jpg)

There are [many use cases](https://kotlinlang.org/docs/js-overview.html#use-cases-for-kotlin-js) for KotlinJS and in this series we'll be creating a Discord bot. Discord is a great platform for keeping in touch with friends, and adding a bot can enrich that experience. We can create this bot by using the NPM module for `DiscordJS`.

So how do you create a discord bot using KotlinJS? Well since this is a somewhat large topic **this post will be the first in a series of three posts**: 
1. In this post we'll go over how kotlinJS uses node modules. This is more of an intro to KotlinJS and doesn't cover Discord.
2. In the next post we'll go over implementing DiscordJS and responding to messages.
3. In the last post we'll add some unit tests to make sure the bot works as expected before deploying.

## Overview

By the end of this series we'll have a small discord bot that responds to a specific message with the computer name it's running on. In this first part the bot will just print to console on startup, but responding to messages will come later in another part.

### Prerequisites
* Some Kotlin Experience
* Some JS Experience
* IntelliJ IDEA (I'm using Community Edition)

## New Project

To get started, first follow the [js project setup](https://kotlinlang.org/docs/js-project-setup.html) on the kotlin site. (Be sure to choose `NodeJS Application`, not `browser`)

This will create a project with a `Main.kt` file and a greeting function. To run the project, call `./gradlew run` in the terminal. You should see `Hello, YOUR_BOT_NAME` in the terminal!

### Importing Node Modules in KotlinJS

Lets start off by getting familiar with how KotlinJS handles Node Modules.

To have the bot send a message with its name, let's use a simple node module called [computer-name](https://www.npmjs.com/package/computer-name#usage). As mentioned in the project setup, node modules are implemented as dependencies in the `build.gradle` file. No need for `require` or special imports. Add this line to your dependencies.

```gradle
dependencies {
    implementation(npm("computer-name", "0.1.0"))
}
```

If you try to call `computerName()` now, you'll get an `Unresolved reference` error. This is because of a crucial difference between javascript and kotlin:

> Javascript is loosely typed and kotlin is strongly typed

So in order to use this function we need to define it by using external functions.

#### External

The kotlin docs mention the [External Modifier](https://kotlinlang.org/docs/js-interop.html#external-modifier), which is used to declare pure javascript code. This tells the compiler that we're expecting this class or function to be defined externally by the node module. Whenever we want to use a module we have to define functions and classes. An important note about this is:

> You only need to define what you are using

So you don't need to define the entire module, just what you need to reference.

So for us to use `computerName()` we need to define it and tell the compiler where it's defined, like so:

```kotlin
@JsModule("computer-name")
external fun computerName(): String
```

Note that we also need to annotate the function with the module name or else we'll hit a runtime error(`compilerReferenceError`).

This definition was easy to figure out based on the npm page, but as we'll see you may have to dig into the documentation and source code of node modules to find the original definition.

##### Module System
Another thing we have to define is the module system. You can find more information [here](https://kotlinlang.org/docs/js-modules.html), but in short KotlinJS supports `UMD`, `AMD` and `commonJS` systems. `commonJS` is widely used for NodeJS so we'll add `useCommonJs()` to our `build.gradle` file.

```gradle
kotlin {
    js(IR) {
        useCommonJs()
    }
}
```

Now try adding `println(computerName())` to `main()` and run the project.

You should see the name of your computer in the terminal! congrats! Now let's look at importing classes.

#### Importing NodeJS Classes

So far we've covered importing a function by using `computerName()` as an example, but that's a small module with one function. It's also important to be able to import classes and interfaces from modules to use in our project.

For this example we'll use [youtube-search](https://www.npmjs.com/package/youtube-search), which is a small module that lets you search for youtube videos (**Note:** You won't get results without a youtube api key, but the calls will still work which is all we need). From the npm page you can see there's a search function that returns custom results. So how do we define this in kotlin?

While there is definition in this module, we'll need to go to [the repository](https://www.npmjs.com/package/youtube-search) to get more information. Lucky for us this module is conveniently written in typescript so it's very easy to find the definition, which is written in `index.d.ts`.

##### Dukat

Before we get into manually converting typescript to kotlin code, I should mention there is a tool to do this automatically called [Dukat](https://kotlinlang.org/docs/js-external-declarations-with-dukat.html). It is a powerful tool that can help with easy conversion, however for this post I want to go over how to manually convert typescript to kotlin so that you have a good understanding of how it works.

##### Manually Converting Typescript

From the repo open the `index.d.ts` file and scroll to the bottom to see the search function.

```javascript
declare function search(
  term: string,
  opts: search.YouTubeSearchOptions,
  cb?: (err: Error, result?: search.YouTubeSearchResults[], pageInfo?: search.YouTubeSearchPageResults) => void
): Promise<{results: search.YouTubeSearchResults[], pageInfo: search.YouTubeSearchPageResults}>;
```

We can see it takes in:
* a string
* a custom options class
* a callback that returns an optional error, results array, and pageInfo array

The fuction then returns a Promise. Luckily kotlin already defines errors in the  `stdlib` and it also includes an import for promises:
```kotlin
import kotlin.js.Promise
```

So the difficult parts are the Results and options. We can see that `YouTubeSearchResults` and `YouTubeSearchPageResults` are interfaces, so we can easily define them like so:
```kotlin
@file:JsModule("youtube-search")
// Note that for multiple definitions in the same file you can use @file

external interface YouTubeSearchPageResults {
  val totalResults: Int
  ...
}

external interface YouTubeSearchResults {
  val id: String
  ...
}
```

Now for `YouTubeSearchOptions` we could do the same approach, but there's another option we can use.

##### Json
We can simply use a `Json` class object, which acts similarly to a `HashMap`. This way we can just pass in what we want without defining the entire interface. This also works well if you can't find a clear definition of an object being passed in.

```kotlin
import kotlin.js.Json

val options = json(
    Pair("maxResults", 1),
    Pair("key", YOUR_YOUTUBE_API_KEY) // Leave this blank if you don't have one
)
```

So from all of this, we can then create the external search function:
```kotlin
@JsModule("youtube-search")
external fun search(
    term: String,
    opts: Json,
    cb: (
        err: Error?,
        result: Array<YouTubeSearchResults>?,
        pageInfo: YouTubeSearchPageResults?
    ) -> Unit // Unit acts as Void in kotlin
) : Promise<Json>

search("your search", options) { err, result, pageInfo ->
    print("Youtube callback $err, $result, $pageInfo\n")
}
```

Try adding this to your project and run. If you don't have an api key you should see this in the terminal:
```
Youtube callback Error: Request failed with status code 403, null, null
```
If you *do* have the api key you should see this:
```
Youtube callback null, [...], [object Object]
```

## Conclusion

Congratulations you have a working KotlinJS project with imported NPM modules! In the next post we'll go over the specifics of creating our Discord bot using DiscordJS.

Thanks for reading! Let me know in the comments if you have questions. Also, you can reach out to me at [@kevinschildhorn](https://twitter.com/KevinSchildhorn) on Twitter.