Why the JS ecosystem is awesome !
Tags: javascript, node, showdev, tutorial
Published: 2019-04-27 17:26:36

The Javascript world is today's the most vibrant development ecosystem. It's popularity propelled it from a simple scripting gadget to the backbone of today's web. Browsers are more powerful each passing day, NPM registry grows to whooping 1 million packages and Node has long shown it's a competent back-end language.

Today, we're going to create a tiny project from scratch together and take full advantage of all that. I want to show you that you don't need years of experience to create something cool.
Buckle up, it's a long ride, but I promise you that by the end you can do it all by yourself.

## Act I: the code

Let's say you have an idea you want to code. No, no, a simpler one. Something that can fit in one function. Something like creating a unique image per username, returning the hour of any timezone or return a random color.

For our purposes, we're going to create a function that give a blank image given dimensions. No big deal, just create a canvas, color it and return the data. We will called it `lorem-pix`.

So, I start every project the same way:

```
$ mkdir lorem-pix
$ cd lorem-pix
$ git init
$ npm init
```

Then, let's create that `index.js` and start coding:

```js
const loremPix = (width, height, color) => {
    const canvas = createCanvas(width, height);
    const ctx = canvas.getContext("2d");

    ctx.fillStyle = color;
    ctx.fillRect(0, 0, width, height);

    return canvas.toDataURL("image/png");
};
module.exports = loremPix;
```

At this point, you should note that `createCanvas` is not defined. If I was in a browser, I'll just have to do `document.createElement("canvas")`, but I already know I don't want to stay in the browser environment and Node has no `Canvas` support. 😱
What could we do ?

## Act II: NPM

With hundreds of thousands of packages, [NPM](https://npmjs.com) potentially hold a solution for any of your problems. As NPM-cli comes bundled with Node, it's safe to say that it's the most influential program in Javascript.

> Don't mix-up NPM and NPM-cli. The former is the public registry holding all the codes while the latter is the utility requesting from the registry.

A [bit of research](https://www.npmjs.com/search?q=canvas) gives me exactly what I need. All left to do is fetching the desired dependency.

```
$ npm install canvas
```

And add a `require` at the top of our `index.js` file.

```js
const { createCanvas } = require("canvas");
```

> Note that Node sadly don't support the [`import` statement](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/import) as of today.

And voilà, our function is now done. We can even publish it at NPM so anyone can use it. Just make sure your `package.json` is correctly filled and we're good to go.

```
$ npm publish
```

Ok, this is already pretty great. We have a function that rely on someone else code and we published it so anyone can rely on us.
But why not go a little further ?

## Act III: CLI

Since [NPM-cli `5.2.0`](https://github.com/npm/npm/releases/tag/v5.2.0), a new command is available: `npx`. Thanks to it, you can execute code hosted on NPM with a single command line. It magically download, install what need to be and run the required binary file.

But we need to have a binary executable. Our JS function is great, but can only be called by a JS script.
Let's remedy that by creating a new file named `cli.js`. Running a plain text file on the operating system is not going to work. We need to tell which program have to parse the code inside it. In our case we need it to be Node (default is `bash` on unix or `cmd.exe` on windows). Fortunately, we can do so with a [shebang](https://www.wikiwand.com/en/Shebang_(Unix)).

```js
#!/usr/bin/env node
const loremPix = require(".");
const { writeFileSync } = require("fs");

const inputs = process.argv.slice(2);

const dataURL = loremPix(...inputs);

writeFileSync("lorem.png", dataURL.split(",")[1], "base64");
console.log("Done creating image.");
```

On the first line, you can notice the shebang thing. Then we import our own library and the Node file-system utility.
Next, we gather the inputs from the command-line arguments, the first one being the program (`node`) and the second one the file executed (`cli.js`). Anything after that is treated as arguments to our function.
Finally, the script [write the file](https://nodejs.org/api/fs.html#fs_fs_writefilesync_file_data_options) using the returned data. Details don't matter, but it need a file name, some data and the encoding method.

After a `chmod` to add execution rights, we are now able to run this script like any program installed on your computer.

```
$ chmod +x cli.js
$ ./cli.js 200 150 red
> Done creating image.
```

Boom! A new file called `lorem.png` appeared right next to the `cli.js` and you guessed it, it's a red image of 200 by 150 pixels.

![Red image](https://thepracticaldev.s3.amazonaws.com/i/o61xkrhzfn1zxjfp2vow.png)
> Incredible! 😮


It means that anyone with Node installed can call `npx` to use our fantastic library.

```
$ npx lorem-pix 200 150 red
```

So now, what if we want to make it available to user even without Node ?

## Act IV: API

Historically, JS was created to be used on back-end and Node is the rightful heir of that history. If our code is deployed on a Node back-end, it can be run with a simple URL call.
I'm going to use [ZEIT's `now`](https://zeit.co/now) because I love it. It's so simple it hurt.

First, we can again add a new file called `api.js` and fill it according to [the documentation](https://zeit.co/docs/v2/deployments/official-builders/node-js-now-node/).

```js
const loremPix = require(".");
const { parse } = require("url");

module.exports = (request, response) => {
    const { query } = parse(request.url, true);
    const { width, height, color } = query;

    const data = loremPix(width, height, color);

    response.writeHead(200, {
        "Content-Type": "image/png",
    });
    response.write(data.split(",")[1], "base64");
    response.end();
};
```

Almost like `cli.js`: import our library, parse the URL for the right parameters and write the result to the response back to the user.

We also need a `now.json` file to configure `now`.

```json
{
    "version": 2,
    "builds": [
        {
            "src": "api.js",
            "use": "@now/node"
        }
    ],
    "routes": [
        {
            "src": "/api/(?<width>[^/]+)/?(?<height>[^/]*)/?(?<color>[^/]*)",
            "dest": "/api.js?width=$width&height=$height&color=$color"
        }
    ]
}
```

First we tell `now` to use Node to run `api.js`. Kind of like the shebang for the `cli.js`.
The hieroglyphs by the end is a regular expression. Users are lazy and so am I. It's easier for me to parse URL like `api.js?width=200&height=150`, but it's more simple for users to write `api/200/150`. The regexp do just that by rerouting requests.

Ok, time to deploy the API.

```
$ npx now login
$ npx now
```

Once done, the program tells you which URL you can use to execute the code. In my case:
[https://lorem-pix-o5fjmzam5.now.sh/api/200/150/red](https://lorem-pix-o5fjmzam5.now.sh/api/200/150/red)

The API return an image whenever someone request it. It means that any HTML page can use it without any installation.

```html
<img src="https://lorem-pix-o5fjmzam5.now.sh/api/200/150/red" alt="placeholder"/>
```

Hol' up a minute !

## Act V: Webservice

Any HTML page can use it ? Why not create our own page ?
We already set `now` to host the JS code, we can add a static HTML page along side it. More than just a landing page to present our project, we can have user inputs with live preview of the result.
Yet another file, this time of course named `index.html`.

```html
<h1>Lorem pix</h1>
<form id="form">
    ...
</form>
<img src="" alt="live preview" id="preview"/>
<script src="front/live-preview.js"></script>
<script>
const form = document.getElementById("form");
const img = document.getElementById("preview");

form.addEventListener("change", () => {
    const { width, height, color } = form;
    livePreview(img, width.value, height.value, color.value);
});
</script>
```

I put the CSS and JS file under the `front` folder just for organization's sake. Here's what our script looks like.

```js
const API_URL = "https://lorem-pix-o5fjmzam5.now.sh/api";

const livePreview = (img, width, height, color) => {
    const url = `${API_URL}/${width}/${height}/${encodeURIComponent(color)}`;
    img.src = url;
    return url;
};
```

Pretty straightforward!
I just used `encodeURIComponent` to avoid the `#` charactere messing with the URL.
Finally, we need tell `now` to serve the static files in `now.json`.

```json
"builds": [
    ...
    {
        "src": "index.html",
        "use": "@now/html-minifier"
    },
    {
        "src": "/front/*",
        "use": "@now/static"
    }
]
```

Let's not forget to deploy again with `npx now` and [we are live](https://lorem-pix-8kqz0zewu.now.sh/).

## Conclusion

What a ride!
Being able to access the same function as a package, a CLI, an API and a front-end webservice is still incredible for me. I highly encourage everyone reading to try and do it's own. You can access the whole [code-base at Github](https://github.com/GMartigny/lorem-pix) and copy/paste from here.
With a little more creativity than just making a blank image, I'm sure all of you can offer some great product. Be sure to share them in comments below.

Peace 🙏