How to Create a Thumbnail-image-for-post API like dev.to with JavaScript in 100 Seconds
Tags: tutorial, notification, javascript, webdev
Published: 2021-05-29 06:19:28

In this article, I will show you step by step to create a thumbnail-image-for-post API like dev.to (just like the image below) by using JavaScript **just in 100 seconds** :alarm_clock:
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9zlh2w4uc7zj7oocxzkp.png)

## :open_file_folder: Repository
- You can download the source code of this article on my Github: https://github.com/richard-wynn/dev-to-thumbnail-image-for-post-api
- If it helps, don't forget to give my repo a star :star:

## :wrench: Necessary Stuffs
- [NodeJS](https://nodejs.org/en)
- [uuid](https://www.npmjs.com/package/uuid): Used to generate a unique id for every created image
- [express](https://www.npmjs.com/package/express): Fast, unopinionated, minimalist web framework for node
- [body-parser](https://www.npmjs.com/package/body-parser): Node.js body parsing middleware
- [moment](https://www.npmjs.com/package/moment): Used to format a given date string
- [puppeteer](https://www.npmjs.com/package/puppeteer): Used to get the snapshot of our html content
- [Postman](https://www.postman.com/downloads): Used to test our API

## :computer: It's Coding Time!
### :one: First phase: Create the image-creator module
Create a new folder then run `npm init -y` inside it to create a `package.json` file.

Next, run the command below to install our necessary packages:
```
$ npm install uuid express body-parser moment puppeteer
```

After that, create a child folder called `public` (this is the place where the output images will be saved) and update the `script` attribute inside `package.json` like this:
```json
...
  "scripts": {
    "start": "node index.js"
  },
...
```

#### image-creator.js
Create an `image-creator.js` file with the following content.
```js
const moment = require('moment');
const { v4 } = require('uuid');
const puppeteer = require('puppeteer');
const fs = require('fs');

const renderHead = () => {
  return `
    <head>
      <style>
        @import url('https://fonts.googleapis.com/css2?family=Roboto&display=swap');

        * {
          margin: 0;
          padding: 0;
          box-sizing: border-box;
        }

        body {
          background-color: #606060;
          padding: 82px;
          font-size: 38px;
          font-family: 'Roboto', sans-serif;
          width: 1600px;
        }

        .post-image-wrapper {
          background-color: white;
          border: 2px solid black;
          border-top-left-radius: 24px;
          border-top-right-radius: 24px;
          padding: 32px 42px;
          box-shadow: 12px 12px 0 black;
          margin: 0 auto;
          padding-top: 62px;
        }

        .post-image-title {
          font-size: 3em;
        }

        .post-image-footer {
          display: flex;
          align-items: center;
          justify-content: space-between;
          margin-top: 132px;
        }

        .post-image-footer-left {
          display: flex;
          align-items: center;
        }

        .post-image-avatar {
          width: 64px;
          height: 64px;
          border-radius: 50%;
          border: 3px solid black;
          object-fit: cover;
          padding: 1px;
          margin-right: 10px;
        }

        .post-image-dot {
          margin: 0 12px;
        }

        .post-image-badge {
          width: 64px;
          height: 64px;
          object-fit: cover;
        }

        #js-badge {
          transform: rotate(-2deg);
        }

        #dev-to-badge {
          transform: rotate(8deg);
          margin-left: 3px;
        }
      </style>
    </head>
  `;
};

const renderBody = (post) => {
  const { title, avatar, full_name, creation_time } = post;

  return `
    <body>
      <div class="post-image-wrapper">
        <div class="post-image-header">
          <h1 class="post-image-title">${title}</h1>
        </div>

        <div class="post-image-footer">
          <div class="post-image-footer-left">
            <img src="${avatar}" alt="Avatar" class="post-image-avatar" />
            <span class="post-image-author">${full_name}</span>
            <span class="post-image-dot">•</span>
            <span class="">${moment(creation_time).format('MMMM DD')}</span>
          </div>

          <div class="post-image-footer-right">
            <div class="post-image-badges">
              <img src="https://i.imgur.com/Xe9C9kI.png" alt="JavaScript Badge" class="post-image-badge" id="js-badge" />
              <img src="https://i.imgur.com/OW7qG1B.png" alt="Dev.to Badge" class="post-image-badge" id="dev-to-badge" />
            </div>
          </div>
        </div>
      </div>
    </body>
  `;
};

const getImageHtml = (post) => {
  return `
    <html lang="en">
      ${renderHead()}
      ${renderBody(post)}
    </html>
  `;
};

const createImage = async (post) => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();

  try {
    const fileName = `${v4()}.png`;

    await page.setContent(getImageHtml(post));

    const content = await page.$('body');
    const imageBuffer = await content.screenshot({ omitBackground: true });

    fs.writeFileSync(`./public/${fileName}`, imageBuffer);

    return fileName;
  } catch (error) {
    return '';
  } finally {
    await browser.close();
  }
};

module.exports = {
  createImage,
};

```

#### index.js
Create an `index.js` file with the following content.

```js
const { createImage } = require('./image-creator');

(async () => {
  const fileName = await createImage({
    title:
      'How to Create a Thumbnail-image-for-post API like dev.to with JavaScript in 100 seconds',
    avatar: 'https://i.imgur.com/bHoLpV6.jpeg',
    full_name: 'Richard Wynn',
    creation_time: '2021-05-29',
  });

  console.log(fileName);
})();
```

Let run `npm start` to run the script and hurrayyy, we've got a new image created inside the public folder :heart_eyes: :heart_eyes:
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/82u3nzn8x1td2hfs0zjp.png)

### :two: Second phase: Create an image-creator API with Express.js
Let update `index.js` with the following content:
```js
const express = require('express');
const bodyParser = require('body-parser');
const path = require('path');
var os = require('os');
const { createImage } = require('./image-creator');
const port = process.env.PORT || 5000;

const app = express();

// Configure body-parser
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());

// Serve static files
app.use('/public', express.static(path.join(__dirname, 'public')));

app.post('/', async (req, res) => {
  try {
    const data = req.body;
    const errors = {};

    if (!data['title'] || data['title'] === '') {
      errors['title'] = 'Title is required!';
    }

    if (!data['avatar'] || data['avatar'] === '') {
      errors['avatar'] = 'Avatar is required!';
    }

    if (!data['full_name'] || data['full_name'] === '') {
      errors['full_name'] = 'Full name is required!';
    }

    if (!data['creation_time'] || data['creation_time'] === '') {
      errors['creation_time'] = 'Creation time is required!';
    }

    if (Object.keys(errors).length > 0) {
      return res.status(500).json({
        status: 'FAILED',
        message: 'Failed to create a thumbnail image for this post!',
        errors,
      });
    }

    const fileName = await createImage(data);

    return res.status(200).json({
      status: 'SUCCESS',
      message: 'Create a thumbnail image successfully!',
      data: `/public/${fileName}`,
    });
  } catch (error) {
    console.log(error);

    return res.status(500).json({
      status: 'FAILED',
      message: 'Failed to create a thumbnail image for this post!',
    });
  }
});

app.listen(port, (err) => {
  if (!err) {
    console.log(`Server is listening on port ${port}...`);
  }
});
```

Let run `npm start` to run the script and use Postman to make a request to `http://localhost:5000` with the following JSON data:
```json
{
    "title": "How to Create a Thumbnail-image-for-post API like dev.to with JavaScript in 100 seconds",
    "avatar": "https://i.imgur.com/bHoLpV6.jpeg",
    "full_name": "Richard Wynn",
    "creation_time": "2021-05-29"
}
```

Click `Send` and boom, we've got a link to the image:
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p18dwhwbmw9nnu776r1q.PNG)

Then make another request to the link to see what we get:
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l1js59apbdaczvwh3oy0.PNG)

Hurrayyyy, it works like a charm :tada::tada:

This is just a for-fun project of mine. It it helps, don't forget to give my Github repo a star or a like to this post :wink::wink:

## :iphone: Keep in Touch
If you like this article, don't forget to follow and stay in touch with my latest ones in the future by following me via:
- **Twitter:** https://twitter.com/RichardWynn01
- **Medium**: https://richard-wynn.medium.com
- **Github:** https://github.com/richard-wynn