How to Build Node.js Application with Bazel
We’re Earthly. We make building software simpler and therefore faster. This article is about when to reach for Bazel. If you are looking for a simpler approach to building monorepos then check us out.
Bazel is an open-source build tool to speed up your builds and tests. Bazel is generally used on very large projects to scale the organization’s codebase. Bazel is a multilingual build system. This guide will help you run and build Bazel with Node.js apps. We will create a Bazel workspace from scratch to build and test Node.js code.
Prerequisites
To follow along with this article, it is helpful to have the following:
- Basic knowledge of working with JavaScript.
- Node.js installed on your computer.
- Bazel installed on your computer.
Setting Up the Bazel Environment
Open your code editor in your preferred working directory. Create two files on the root directory:
WORKSPACE.bazel
: For defining the workspace environment.BUILD.bazel
: For defining builds.
Edit the WORKSPACE.bazel
as explained in the following step-by-step instructions:
Start by defining the workspace by giving it a name. Preferably the name of the directory you are currently working at.
workspace(
name = "your_workspace_name",
)
- Load the
http_archive
package and define it:
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
name = "build_bazel_rules_nodejs",
sha256 = "c29944ba9b0b430aadcaf3bf2570fece6fc5ebfb76df145c6cdad40d65c20811",
urls = ["https://github.com/bazelbuild/rules_nodejs/releases/download/5.7.0/rules_nodejs-5.7.0.tar.gz"],
)
- Load the Bazel Node.js rules dependencies and call the function after loading:
load("@build_bazel_rules_nodejs//:repositories.bzl",\
"build_bazel_rules_nodejs_dependencies")
build_bazel_rules_nodejs_dependencies()
- Load Node.js from node_repositories and call it too:
load("@build_bazel_rules_nodejs//:index.bzl", "node_repositories")
node_repositories()
- Load NPM and define the residing place for
package.json
andpackage-lock.json
:
load("@build_bazel_rules_nodejs//:index.bzl", "npm_install")
npm_install(
= "npm",
name = "//:package.json",
package_json = "//:package-lock.json",
package_lock_json )
On the project root directory, create a package.json
file. Edit the package.json
file as below:
{"name":"your_project_name",
"version":"0.0.1",
"dependencies": {
"express":"4.17.3" // for creating a Node.js web server
,
}"devDependencies": { // jasmine : Bazel's test runner
"@bazel/jasmine": "5.3.0",
"jasmine":"4.0.2",
"jasmine-core":"4.0.1"
} }
- Install the above dependencies using Bazel by running the following command:
bazel run @nodejs_host//:npm -- install
From the above command, Bazel will create a couple of additional directories for managing the Node.js dependencies: package-lock.json
and node_modules
.
Implementing and Testing a Simple Calculator Application
Let’s now test the create Bazel environment using a Node.js application. On the project root directory, create a directory and name it apps
. Inside the apps
directory, create a simple_calculator
directory. Inside the simple_calculator
, create three files:
calculator.js
: For defining the logic. Editcalculator.js
as follows:
.exports = class Calculator {
modulesubtract(x,y){ // returning subtraction of two numbers.
return x - y;
} }
calculator.spec.js
: For defining the testing logic. Editcalculator.spec.js
as follows:
const Calculator = require('./calculator');
const calculator = new Calculator();
it('10 - 4 = 6 >', () => {
// testing the result from the calculator class if it will equal 6
const expected_value = 6;
expect(calculator.subtract(10,4)).toEqual(expected_value);
})
BUILD.bazel
: For defining the Bazel build dependencies and steps. EditBUILD.bazel
as follows:
load("@npm//@bazel/jasmine:index.bzl","jasmine_node_test")
#loading the node dependencies
filegroup(
name="node_calculator",
srcs=["calculator.js"],
visibility = ["//apps/node_web:__pkg__"]
#full visibility of the apps folder
)
jasmine_node_test(
name="calculator_test", # name
srcs=["calculator.spec.js"], # spec files
data = [":node_calculator"]
)
To test the functionality, run the following command:
bazel test //...
Your response should be similar to:
Exposing the Calculator Application On a Web Server
Let’s Now load the calculator app to the web while implementing the Bazel builds. Create another directory on the apps
folder and name it node_web
. In the node_web
folder, create two files:
index.js
: For starting the web server and handling routes. Edit theindex.js
as follows:
const express = require('express');
const Calculator = require('../node_calculator/calculator');
const app = new express();
const calculator = new Calculator();
.get('/',(req,res) => {
app.send(`The result of 10 - 4 = ${calculator.subtract(10,4)}`);
res
})
.listen(8080, () => console.log(`listening on port 8080`)); app
BUILD.bazel
: For handling the Bazel build steps. Edit theBUILD.bazel
as follows:
load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_binary")
nodejs_binary(
name = "node_web",
data = [
"//apps/node_calculator:node_calculator",
"@npm//express:express"
],
entry_point = ":index.js",
)
Run the project by executing the following command:
bazel run apps/node_web
You will get such a response on the terminal:
From above, the server is running on port 8080. Proceed to http://localhost:8080
. You will get the following calculator response:
Conclusion
This guide helped us create Node.js with Bazel. We were able to configure Bazel, set up Bazel builds, and, most importantly, run tests using Bazel for the Node.js app. I hope you found this guide helpful.
Bazel isn’t the only solution for the automation of building and testing software. Earthly provides a convenient framework to build images or stand-alone artifacts by leveraging containers for the execution of pipelines.
A Earthfile for building, testing, and containerizing our app could look like this:
VERSION 0.7FROM node:14
WORKDIR /app
deps:COPY package.json package-lock.json ./
RUN npm ci
build:FROM +deps
COPY . .
RUN npm run build
test:FROM +build
RUN npm test
run:FROM +build
ENTRYPOINT ["npm", "start"]
SAVE IMAGE --push npm-example:latest
Earthly combines the best ideas from Dockerfiles and Makefiles into one specification, making the containers self-contained, repeatable, portable, and parallel.
Earthly makes CI/CD super simple
Fast, repeatable CI/CD with an instantly familiar syntax – like Dockerfile and Makefile had a baby.