DEV Community

loading...

How to Create a Thumbnail-image-for-post API like dev.to with JavaScript in 100 Seconds

Richard Wynn
👋 Hi, I'm Richard Wynn, a Senior Full-stack Software Engineer with a joy of Coding, Writing and Sharing knowledge
・5 min read

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
Alt Text

📂 Repository

🔧 Necessary Stuffs

  • NodeJS
  • uuid: Used to generate a unique id for every created image
  • express: Fast, unopinionated, minimalist web framework for node
  • body-parser: Node.js body parsing middleware
  • moment: Used to format a given date string
  • puppeteer: Used to get the snapshot of our html content
  • Postman: Used to test our API

💻 It's Coding Time!

1️⃣ 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
Enter fullscreen mode Exit fullscreen mode

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:

...
  "scripts": {
    "start": "node index.js"
  },
...
Enter fullscreen mode Exit fullscreen mode

image-creator.js

Create an image-creator.js file with the following content.

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,
};

Enter fullscreen mode Exit fullscreen mode

index.js

Create an index.js file with the following content.

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);
})();
Enter fullscreen mode Exit fullscreen mode

Let run npm start to run the script and hurrayyy, we've got a new image created inside the public folder 😍 😍
Alt Text

2️⃣ Second phase: Create an image-creator API with Express.js

Let update index.js with the following content:

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}...`);
  }
});
Enter fullscreen mode Exit fullscreen mode

Let run npm start to run the script and use Postman to make a request to http://localhost:5000 with the following JSON data:

{
    "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"
}
Enter fullscreen mode Exit fullscreen mode

Click Send and boom, we've got a link to the image:
Alt Text

Then make another request to the link to see what we get:
Alt Text

Hurrayyyy, it works like a charm 🎉🎉

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 😉😉

📱 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:

Discussion (0)