How to Track "Blipped" Marvel Characters with Node.js & CockroachDB Serverless
In this tutorial, we will create a simple web application using Node.js, Express, Sequelize, and CockroachDB Serverless. The app will display a list of Marvel characters with their images, names, and whether they were affected by the Blip (also known as the Decimation or the Snap) in Avengers: Infinity War and Endgame movies. To follow along, you'll need to have Node.js installed on your machine. You can download it from https://nodejs.org/. Additionally, install Sequelize by running `npm install --save sequelize`. First, let's create a new directory for our project and initialize it with npm: ```bash mkdir the-blip && cd the-blip npm init -y ``` Next, we need to install Express and CockroachDB driver: ```bash npm install --save express @cockroachdb/ cockroach-sdk ``` Now, let's create a new file called `app.js` in the root of our project folder. This will be our main application file. In this tutorial, we will use Pug as our template engine. If you don't have it installed yet, run: ```bash npm install --save pug ``` Now, let's set up the basic structure of our app in `app.js`. We will start by importing required modules and setting up some environment variables: ```javascript const express = require('express'); const { Sequelize } = require('sequelize'); const CockroachDB = require('@cockroachdb/ cockroach-sdk'); require('dotenv').config(); const app = express(); app.set('view engine', 'pug'); // Set up environment variables from .env file const port = process.env.PORT || 3000; const marvelApiKey = process.env.MARVEL_PUBLIC_KEY; const marvelPrivateKey = process.env.MARVEL_PRIVATE_KEY; ``` Next, let's create a new Sequelize instance and connect it to our CockroachDB Serverless cluster: ```javascript // Set up Sequelize connection const sequelize = new Sequelize( process.env.DB_NAME, process.env.DB_USER, process.env.DB_PASSWORD, { host: process.env.DB_HOST, dialect: 'postgres', port: process.env.DB_PORT, pool: { max: 5, min: 0, acquire: 30000, idle: 10000 } } ); ``` Now, let's define our `Character` model using Sequelize. We will also create a new table in the database if it doesn't exist yet: ```javascript // Define Character model const Character = sequelize.define('character', { marvelId: { type: Sequelize.INTEGER, primaryKey: true }, name: Sequelize.STRING, thumbnail: Sequelize.STRING, blip: Sequelize.BOOLEAN }); // Sync the database with our models sequelize.sync().then(() => { console.log('Database & tables created!'); }).catch((error) => { console.error('Error creating database:', error); }); ``` Next, let's set up some routes for our app. We will create three main routes: `/`, `/sync`, and `/:id`. The first one will render the list of characters, the second one will synchronize character data from Marvel API, and the last one will update a specific character's blip status. ```javascript // Route for getting all characters app.get('/', async (req, res) => { // -- Get All Characters -- const characters = await Character.findAll(); res.render('index', { title: 'The Blip (All Characters)', characters: characters }); }); // Route for syncing character data from Marvel API app.get('/sync', async (req, res) => { // -- Retrieve and Insert Character Data -- let result = await getCharacters(0); // Retrieve once to get the total const total = result.data.total; // Clear the table await Character.destroy({ truncate: true }); let batch = []; for (let offset = 0; offset < total; offset += 100) { // Get Character Data result = await getCharacters(offset); const characters = result.data.results; // Bulk Create for (let i = 0; i < characters.length; i++) { const isBlipped = blipped.some(c => characters[i].name.includes(c)); const isSafe = notBlipped.some(c => characters[i].name.includes(c)); batch.push({ marvelId: characters[i].id, name: characters[i].name, thumbnail: `${characters[i].thumbnail['path']}.${characters[i].thumbnail['extension']}`, blip: isBlipped ? true : (isSafe ? false : null) }); } } const c = await Character.bulkCreate(batch); res.json({ success: true }); }); // Route for updating character's blip status by ID app.get('/blip/:id', async (req, res) => { // -- Blip Character by ID -- const character = await Character.update({ blip: true }, { where: { marvelId: req.params['id'] } }); res.json(character); }); // Route for updating character's unblip status by ID app.get('/unblip/:id', async (req, res) => { // -- Unblip Character by ID -- const character = await Character.update({ blip: false }, { where: { marvelId: req.params['id'] } }); res.json(character); }); ``` Now, let's define the `getCharacters()` function that retrieves character data from Marvel API using their public and private keys stored in environment variables. We will also use CockroachDB to store the retrieved data: ```javascript // Function for getting character data from Marvel API async function getCharacters(offset = 0) { const baseUrl = "https://gateway.marvel.com"; const ts = new Date().getTime(); // Generate MD5 hash const hash = crypto.createHash('md5').update(`${ts}${process.env.MARVEL_PRIVATE_KEY}${process.env.MARVEL_PUBLIC_KEY}`).digest('hex'); let result = await fetch(`${baseUrl}/v1/public/characters?ts=${ts}&hash=${hash}&apikey=${process.env.MARVEL_PUBLIC_KEY}&limit=100&offset=${offset}`).then(r => r.json()); return result; } ``` Finally, let's start our server and listen for incoming requests: ```javascript // Start the server app.listen(port, () => { console.log(`App listening at http://localhost:${port}`); }); ``` Now, we need to create a new view file called `index.pug` in the `views` folder. This will be our main template for displaying character data. In this tutorial, we will use Bootstrap as our CSS framework. If you don't have it installed yet, run: ```bash npm install --save bootstrap ``` Now, let's set up the basic structure of our view in `index.pug`. We will include Bootstrap styles and scripts, display a title for the page, and render character data using a loop: ```jade doctype html html(lang="en") head meta(charset="utf-8") meta(name="viewport" content="width=device-width, initial-scale=1") link(href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet") script(src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js") title= title body div.container h1 <a href="https://en.wikipedia.org/wiki/The_Blip">The Blip</a> Marvel Character List small a(href="https://marvel.com") Data provided by Marvel. © 2021 MARVEL br button.btn.btn-primary(id="sync-btn" onclick="syncAll()") Sync Character Data from Marvel table tr th Image th Name th Blipped each c in characters tr td img(src=c.thumbnail height="32px") td= c.name if c.blip === true td button.btn.btn-danger(id=\`btn-${c.marvelId}\` onclick=\`callApi("unblip", ${c.marvelId})\`) Blipped (Click to Unblip) else if c.blip === false td button.btn.btn-success(id=\`btn-${c.marvelId}\` onclick=\`callApi("blip", ${c.marvelId})\`) Safe (Click to Blip) else td button.btn.btn-secondary(id=\`btn-${c.marvelId}\` onclick=\`callApi("blip", ${c.marvelId})\`) Unknown (Click to Blip) ``` Now, let's add some JavaScript code to our view for handling button clicks and making AJAX requests to update character data: ```javascript async function syncAll() { document.getElementById("sync-btn").innerText = "Syncing..."; let result = await fetch("/sync").then(r => r.json()); // Reload the page to grab the new data location.reload(); } async function callApi(api, id) { let result = await fetch(`/${api}/${id}`).then(r => r.json()); let elem = document.getElementById(`btn-${id}`); if (api === "blip") { elem.innerText = "Unblip"; elem.classList.remove("btn-success", "btn-secondary"); elem.classList.add("btn-danger"); elem.onclick = () => callApi("unblip", id); } else { elem.innerText = "Blip"; elem.classList.remove("btn-danger", "btn-secondary"); elem.classList.add("btn-success"); elem.onclick = () => callApi("blip", id); } } ``` Now, we need to create a new `.env` file in the root of our project folder and add the following environment variables: ``` DB_NAME=the-blip DB_USER=your-username DB_PASSWORD=your-password DB_HOST=free-tier1.aws-us-east-1.cockroachlabs.cloud DB_PORT=26257 MARVEL_PUBLIC_KEY=your-public-key MARVEL_PRIVATE_KEY=your-private-key ``` Replace `your-username`, `your-password`, and `your-keys` with your actual values. You can get them from CockroachDB Serverless dashboard or Marvel Developer Portal. Now, let's start our server by running: ```bash node app.js ``` You should see the following output in your terminal: ``` App listening at http://localhost:3000 Database & tables created! ``` Open your browser and navigate to `http://localhost:3000`. You should see a list of Marvel characters with their images, names, and whether they were affected by the Blip. Try clicking on some buttons to update character data and reload the page to see the changes. That's it! We have successfully created and deployed a simple web application using Node.js, Express, Sequelize, CockroachDB Serverless, and Pug template engine. You can now build upon this project by adding more features or creating an entirely different app.
Company
Cockroach Labs
Date published
Oct. 21, 2021
Author(s)
Raphael Mun
Word count
4130
Hacker News points
None found.
Language
English