Using promises instead of callback functions

javascript

Before promises come to JavaScript we had to rely on callback functions when we needed something to be done after an async action. Let's say we have functions first() and second() and we want them to run in sequence, however first() has some async actions happening inside it. This is how we used to run them before promises:


function first(callback) {
  console.log("first starting...");
  
  setTimeout(() => {
    // do some stuff here...
    callback();
  }, 500);
}

function second() {
  console.log("second starting...");
}

first(second);

Now with promises we can run these functions in sequence without using a callback:


function first() {
  console.log("first starting...");
  
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      // do some stuff here...
      resolve();
    }, 500);
  });
}

function second() {
  console.log("second starting...");
}

first().then(second);

In other words, promises allow us to chain functions using their .then() method which is a cleaner syntax than using callback functions, specially when more functions need to run after one another.

Sorting items in an array by an arbitrary order

javascript

Sometimes you need to display data that you fetch from backend in a custom order in frontend. The following snippet shows how to order objects in our array using a custom order defined in another array. Note that to avoid mutation we need to use WEB API's structuredClone() method instead of spread syntax (...) as spread syntax only does a shallow copy of array items and hence won't be able to clone objects inside an array.

const myArray = [
  {name: 'firstname', value: 'Tom'},
  {name: 'age', value: 61},
  {name: 'lastname', value: 'Cruise'}
];
const mySortOrder = ['firstname', 'lastname', 'age'];
const sortArray = (myArray, mySortOrder) => {
  myArray.sort((a, b) => {
    const indexA = mySortOrder.indexOf(a.name);
    const indexB = mySortOrder.indexOf(b.name);
    return indexA - indexB;
  });
  return myArray;
}
const sortedArray = sortArray(structuredClone(myArray), mySortOrder);
console.log(sortedArray);

Circle of Data

javascript

I put together an example of transferring data between browser (JavaScript) and server (Node.js/MongoDB) without any frameworks. The example can run via express.js server locally or without a server on a serverless cloud platform such as Vercel. Check out the demo at https://form-liart-five.vercel.app/ or the code on GitHub

Securing API endpoints with JWT tokens

node.js authentication

To secure REST API endpoints only requests with a valid access token in their authorization header should reach the endpoints. Requests without an authorization header should return 403 Forbidden error and those without a valid token should return 401 Unauthorized error. Such verification can be done in a middleware function so that API itself does not need to be touched or know anything about authentication at all.

The following code shows example of a Node.js Express middleware that implements such verification using jsonwebtoken package from npm:

import jwt from 'jsonwebtoken';
export default (req, res, next) => {
  const authHeader = req.headers['authorization'];

  if (authHeader) {
    const token = authHeader.split(' ')[1];
    try {
      const decoded = jwt.verify(token, 'mySecret');
      next();
    } catch(err) {
      res.sendStatus(401);
    }
  } else {
    res.sendStatus(403);
  }
}

When a user authenticates himself by posting credentials to the server, server will generate an access token such as a JWT and send it back to user in body of the response.

import jwt from 'jsonwebtoken';
export default async (req, res) => {
  const {username, password} = req.body;
  if (username === 'lorem' && password === 'ipsum') {
    const token = jwt.sign({
      username: username
    }, 'mySecret', {expiresIn: '8h'});
    res.json({
      "access_token": token
    });
  } else {
    res.sendStatus(403);
  }
}

This token then will be fetched on client side and will be sent in authorization header of requests to the API.

Animated loader in CSS

css

Add animated loading icon to any HTML element by adding a "loading" class and the following css rules:

.loading::before {
  content: ' ';
  width: 1em;
  height: 1em;
  border: 0.2em solid #999;
  border-top-color: transparent;
  border-radius: 50%;
  animation: loadingspin 1s linear infinite;
  }

  @keyframes loadingspin {
  100% {
      transform: rotate(360deg)
  }
}

Demo on codepen

Basic Authentication

authentication

The easiest way to password protect a resource on Web is to use Basic Authentication scheme. Bearer authentication is another common, but more complex authentication scheme which I will talk about in another article. In basic authentication two HTTP headers are used: authorization and www-authenticate. When an http request without authorization header is received by the server, it would return a response with 401 (unauthorized) status and the header www-authenticate: basic. This header tells user agent to prompt user to enter credentials required to access the resource. Credentials entered by the user are then encoded in username:password format and sent to the server in a new request's authorization header. When server receives this request it checks whether the value of authorization header matches with credentials stored on the server or not. If it does, server responds with the requested resource, otherwise it responds with 401 (unauthorized) status and the header “www-authenticate: basic”.

Example of basic authentication implemented in Node.js: https://github.com/smohadjer/vercel-basic-auth/blob/master/middleware.js

Basic Authentication demo (Use admin for both username and password)

Using DOMParser to convert markup to DOM element

javascript

If you have some markup as a string of text and want to add it to a HTML page via JavaScript, the old way was to use innerHTML property like this:

const myHtmlString = '<h1>Hello World</h1>';
const myElement = document.querySelector('header');
myElement.innerHTML = myHtmlString;

However this replaces the whole content of element with new HTML string which may not always be desirable. Often you just want to add the new HTML as a child to an element in your page. In such cases you can use DOMParser interface like this:

const myHtmlString = '<h1>Hello World</h1>';
const myHtmlElement = new DOMParser().parseFromString(myHtmlString, 'text/html').body.firstChild;
const myElement = document.querySelector('header');
myElement.append(myHtmlElement);

Switching from Rollup to Esbuild for bundling frontend resources

npm node.js

I recently refactored my frontend build, switching from Rollup to Esbuild for bundling JavaScript and CSS. The change reduced the build time to less than half (from 21s to 10s). Below are a few things I learnt during this change:

Open-Source Web Development Tools

tools
Responsive Image Breakpoints Generator Easily generate the optimal responsive image dimensions
Live Server Development server with live reload capability for fast prototyping
Thunder Client Rest Client for Testing APIs in VS Code
esbuild Bundle modules and scripts for better performance in browsers
LocaPing Traceroute From Multiple Locations. Enter your site's domain name and select a country to find out location and performance of the server hosting your domain to users visiting from that country.
iview Use to find out X and Y coordinates of any point in an image.
Image to Base64 Encode images to Base64

JavaScript Best Practices

javascript

Using Location API on anchor elements

javascript

Did you know that a HTML anchor element can be treated as a Location object? This means similar to how you can use window.location to read or write different parts of a URL, you can do the same with an anchor element. For example if you have a link like this:

<a id="myLink" href="https://mydomain/products.html?category=clothing#productName">test</a>

You can get the value of hash in this way:

const hash = document.getElementById('myLink').hash.substring(1);
// returns productName

You can use different properties of Location object on an anchor element and same as with window.location you can both read and write these properties. For example to get the value of category from previous link you can use this:

const params = document.getElementById('myLink').search;
const category = new URLSearchParams(params).get('category'); // clothing

For more info see: https://developer.mozilla.org/en-US/docs/Web/API/Location

Fixing TypeScript error property does not exist on type EventTarget

typescript

When working with event listeners in TypeScript one often runs into error that a particular property does not exist on type EventTarget. While it's relatively easy to fix this error using type "any" or by typecasting the event target using "as", none of these solutions are optimal since they simply disable TypeScript's type checking, which is what we are using TypeScript for in the first place!

The better way to fix this error is to use JavaScript's instanceof operator which you can read about at: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/instanceof.

Example of code that throws error:


document.querySelector('p').addEventListener('click', (event) => {
alert(event.target.textContent);
});

Error:

Property 'textContent' does not exist on type 'EventTarget'.

Solution:


document.querySelector('p').addEventListener('click', (event) => {
if (event.target instanceof Element) {
  alert(event.target.textContent);
}
});

Closing an open port via Mac's terminal

mac

Use below command in terminal to get a list of all open ports:

netstat -an | grep LISTEN

Find the process ID (PID) of the port you wish to close. You may need to use sudo.

lsof -i :[portNumber]

Then use the following command to kill the process listening on your port. You may need to use sudo with kill -9 parameter.

kill PID

Deploying a static HTML page and a serverless function (Node.js) live via Vercel

vercel node.js

In this example you will create a static HTML page that calls an api (Node.js serverless function) which responds by returning the word "Hello World" and displaying it in the browser. You can see a deployed version at: https://test-pi-tawny.vercel.app/

We will deploy and host both the HTML page and the Node.js api on Vercel using Vercel's cli, so you should register for a free account at vercel.com if you don't already have one and install vercel's command line via:


npm install -g pnpm
pnpm i -g vercel

  1. Run the following in terminal to create file and folder structure for "test" project:
    
    mkdir test
    cd test
    mkdir public api
    cd api
    touch index.js
    cd ../public
    touch index.html
    cd ..
    
  2. Open index.html in your editor and paste the following snippet there:
    
    <!DOCTYPE html>
    <html lang="en">
        <head>
            <meta charset="utf-8" />
            <title>Hello World</title>
            <meta name="viewport" content="width=device-width, initial-scale=1" />
        </head>
        <body>
            <p id="test"></p>
            <script>
              fetch('api/index.js')
              .then((response) => response.text())
              .then((text) => {
                console.log(text);
                document.getElementById('test').innerHTML = text;
              });
            </script>
        </body>
    </html>
    
  3. Open index.js in your editor and paste the following snippet there:
    
    export default function handler(req, res) {
        res.setHeader('Content-Type', 'text/plain')
        res.write('Hello World')
        res.end()
    }
    
  4. In cli/terminal run:
    vercel dev
    and select default answers to all questions. Vercel cli will create the test project under your existing Vercel account and start a local server at: http://localhost:3000/ where you can see your index.html and your api can be reached at: http://localhost:3000/api/. If everything is working properly, you should see a "Hello World" message in your browser.
  5. To deploy your project to server for preview purposes run in terminal:
    vercel deploy
    And if you are happy with result and want to deploy it to production run:
    vercel --prod
    The preview and production URLs are available in your project on vercel.com under "Deployment" tab.

Responsive images that do not scale up beyond their intrinsic size

css responsive

Usually all you need to make an image resize based on size of screen is to add one simple CSS rule:

img {
  width: 100%;
}

However if the screen resolution is higher than width of your image, this would result in a blurred image as there is simply not enough data in image to render it at higher resolutions. In such cases it might be more desirable to keep the image responsive, but not allow it to get larger than it's actual size. This can be achieved by the following CSS rule:

img {
  width: auto;
  max-width: 100%;
}

Deploy to Vercel only when a specific file or folder in repository is changed

vercel github

When you install Vercel application on GitHub and give it access to your repository, GitHub will trigger a deployment on Vercel every time there is a new commit in your main branch, however if your API on Vercel only needs to be updated when a certain file or folder is changed, these deployments are unnecessary. You can avoid unnecessary deployments to Vercel using GitHub Actions as described here: https://vercel.com/guides/how-can-i-use-github-actions-with-vercel.

Another advantage of using GitHub Actions with Vercel is that you can uninstall Vercel application from GitHub as Vercel no longer needs access to your GitHub repository. You simply need to create a token on Vercel and store it along with your Vercel Org and Project IDs (found in .vercel/project.json in root of your project) in secrets on Github at https://github.com/your-username/your-repository/settings/secrets/actions

vercel secrets on GitHub

Disabling preview deployments in Vercel

vercel

If you don't want Vercel to do preview deployments for other branches of your repository you can disable preview deployments by going to your project's settings > git > Ignored Build Step and add the following command:

if [ "$VERCEL_ENV" == "production" ]; then exit 1; else exit 0; fi

This is probably the easiest way. Another way would be to add a vercel.json to all branches and set "deploymentEnabled" to false for any branches that should not trigger deployment, like below:

{
  "git": {
    "deploymentEnabled": {
      "gh-pages": false
    }
  }
}

More info: