Categories
JavaScript JavaScript Basics

How to do Exponentiation in JavaScript

There are multiple to compute exponents with JavaScript.

The newest way is the exponentiation operator **, available with ES2016 or higher.

For example, we can do this:

const a = 2 ** 3; // 8

It is right associative, so a ** b ** c is equal to a ** (b ** c). This works with all exponents.

For example:

const a = 2 ** (3 ** 4);
const b = 2 ** 3 ** 4;
a == b // true, both are 2.4178516392292583e+24

Detail browser compatibility is available at https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#Browser_compatibility

We can also use the Math.pow function, like this:

const a = Math.pow(2,3) // 8

It takes 2 arguments, the first is the base and the second is the exponent. Math.pow works with all exponents.

Math.pow is compatible with all recent browsers.

Categories
Vue

How to Use VeeValidate 3 for Form Validation

VeeValidate 3 completely changed how form validation it’s done compared to the previous version. While previous versions add form validation rules straight in the input, VeeValidate 3 wraps the component provided by it around the input to provide form validation for the component. We wrap ValidationProvider component around an input to add form validation capabilities to the input.

The built in rules are now included with you start the app. They are all registered one by one in the entry point of the app instead of just calling Vue.use on the library in order to use the rules.

Form validation errors are passed into the input from the scoped slot which is available when you add an input inside ValidationProvider . For example, if we have:

<ValidationProvider name="email" rules="required">
  <div slot-scope="{ errors }">
    <input v-model="name">
    <p>{{ errors[0] }}</p>
  </div>
</ValidationProvider>

we get errors from the ValidationProvider in the code above.

With this arrangement, specifying custom rules is easier than in version 2 as you will see in the app we will build below.

In this article, we will build a simple expense tracker with a form to add the description, amount and date of the expense and a table to display the data. We will also add buttons to let user delete expenses. In addition, there will be a page showing a line chart of the expenses sorted by date.

The back end will be built with Koa to keep it simple. We will use the latest version of Vue.js with the latest version of BootstrapVue to build the UI. Vee-Validate will be used for form validation and Vue-Chartjs will be used for the line chart.

Back End

To start, we build a simple back end to store the expenses. Create a project folder then create a backend folder to store the back end code.Now we can build the back end, we run npm init and answer the questions by entering the default values. Then we install our own packages. Koa comes with nothing, so we need to install a request body parser, a router, CORS add-on to enable cross domain requests with front end and add libraries for database.

To install all these packages, run npm i @babel/cli @babel/core @babel/node @babel/preset-env @koa/cors koa-bodyparser koa-router sequelize sqlite3 . We need the Babel packages for running with the latest JavaScript features. Sequelize and SQLite are the ORM and database that we will use respectively. The Koa packages are for enabling CORS, parsing JSON request body, and enable routing respectively.

Next run:

npx sequelize-cli init

to create database boilerplate code.

Then we run:

npx sequelize-cli --name Expense --attributes description:string,amount:float,date:date

to create a Expense table with the fields and data types listed in the attributes option.

After that is done, run npx sequelize-cli db:migrate to create the database.

Next create app.js in the root of the backend folder and add:

const Koa = require("koa");
const cors = require("@koa/cors");
const Router = require("koa-router");
const models = require("./models");
const bodyParser = require("koa-bodyparser");

const app = new Koa();
app.use(bodyParser());
app.use(cors());
const router = new Router();

router.get("/expenses", async (ctx, next) => {
  const Expenses = await models.Expense.findAll();
  ctx.body = Expenses;
});

router.post("/expenses", async (ctx, next) => {
  const Expense = await models.Expense.create(ctx.request.body);
  ctx.body = Expense;
});

router.delete("/expenses/:id", async (ctx, next) => {
  const id = ctx.params.id;
  await models.Expense.destroy({ where: { id } });
  ctx.body = {};
});

app.use(router.routes()).use(router.allowedMethods());

app.listen(3000);

This is the file with all the logic for our app. We use the Sequelize model we created by importing the models module that is created by running sequelize-cli init .

Then we enable CORS by adding app.use(cors()); JSON request body parsing is enabled by adding app.use(bodyParser()); . We add a router by adding: const router = new Router(); .

In the GET expense route, we get all the expenses. The POST is for adding a Contact. The PUT route is used for updating an existing expense by looking it up by ID. And the DELETE route is for deleting a expense by ID.

Now the back end is done. It is that simple.

Front End

To start building the front end, we add a frontend folder in the project’s root folder and then go into the frontend folder and run:

npx @vue/cli create .

When we run the wizard, we will manually select the options, and choose to include Vuex and Vue Router and use SCSS, and NPM for package management.

Next we install some packages. We will use Axios for making requests, Moment for manipulating dates, BootstrapVue for styling, Vee-Validate for form validation and Vue-Chartjs for displaying our chart.

To install everything, we run:

npm i axios bootstrap-vue chartjs vue-chartjs vee-validate moment

With all the packages installed, we can start writing code.

In the src folder, create a charts folder and create a file called ExpenseChart.vue inside it. In the file, add:

<script>
import { Line } from "vue-chartjs";

export default {
  extends: Line,
  props: ["chartdata", "options"],
  mounted() {
    this.renderChart(this.chartdata, this.options);
  },
  watch: {
    chartdata() {
      this.renderChart(this.chartdata, this.options);
    },
    options() {
      this.renderChart(this.chartdata, this.options);
    }
  }
};
</script>

<style>
</style>

We specify that this component accepts the chartdata and options props. We call this.renderChart in both the mounted and watch blocks so that the chart will be updated whenever the props change or when this component first loads.

Next we create a filter to format dates. Create a filters folder in the src folder and inside it, create date.js . In the file, we add:

import * as moment from "moment";

export const dateFilter = value => {
  return moment(value).format("YYYY-MM-DD");
};

to take in a date and the return it formatted in the YYYY-MM-DD format.

Then we create a mixin for the HTTP request code. Create a mixins folder in the src folder and in it, create a requestsMixin.js and add:

const APIURL = "[http://localhost:3000](http://localhost:3000)";
const axios = require("axios");

export const requestsMixin = {
  methods: {
    getExpenses() {
      return axios.get(`${APIURL}/expenses`);
    },

addExpense(data) {
      return axios.post(`${APIURL}/expenses`, data);
    },

deleteExpense(id) {
      return axios.delete(`${APIURL}/expenses/${id}`);
    }
  }
};

This allows us to call these functions in any component when the mixin is included in the component.

Next in the views folder, we create our components. Create a Graph.vue file in the views folder and add:

<template>
  <div class="about">
    <h1 class="text-center">Expense Chart</h1>
    <expense-chart :chartdata="chartData" :options="options"></expense-chart>
  </div>
</template>

<script>
import { requestsMixin } from "../mixins/requestsMixin";
import * as moment from "moment";

export default {
  name: "home",
  mixins: [requestsMixin],
  data() {
    return {
      chartData: {},
      options: { responsive: true, maintainAspectRatio: false }
    };
  },
  beforeMount() {
    this.getAllExpenses();
  },

methods: {
    async getAllExpenses() {
      const response = await this.getExpenses();
      const sortedData = response.data.sort(
        (a, b) => +moment(a.date).toDate() - +moment(b.date).toDate()
      );
      const dates = Array.from(
        new Set(sortedData.map(d => moment(d.date).format("YYYY-MM-DD")))
      );
      const expensesByDate = {};
      dates.forEach(d => {
        expensesByDate[d] = 0;
      });

dates.forEach(d => {
        const data = sortedData.filter(
          sd => moment(sd.date).format("YYYY-MM-DD") == d
        );
        expensesByDate[d] += +data
          .map(a => +a.amount)
          .reduce((a, b) => {
            return a + b;
          });
      });

this.chartData = {
        labels: dates,
        datasets: [
          {
            label: "Expenses",
            backgroundColor: "#f87979",
            data: Object.keys(expensesByDate).map(d => expensesByDate[d])
          }
        ]
      };
    }
  }
};
</script>

We display the ExpenseChart that we created earlier here. The data is populated by getting them from back end. The this.getExpenses function is from the requestMixin that we included in this file. To generate the chartData , we sort the data by date, and then set the dates as the labels, and in the datasets , we have the data for the line, which is the amount of the expense. We add up all the expenses for each day and convert it into an array with:

const dates = Array.from(
        new Set(sortedData.map(d => moment(d.date).format("YYYY-MM-DD")))
      );
const expensesByDate = {};
dates.forEach(d => {
  expensesByDate[d] = 0;
});

dates.forEach(d => {
  const data = sortedData.filter(
    sd => moment(sd.date).format("YYYY-MM-DD") == d
  );
  expensesByDate[d] += +data
    .map(a => +a.amount)
    .reduce((a, b) => {
      return a + b;
    });
});

In the code above, we convert the expenses into a dictionary with the date as the key and the total expenses as the value.

We make the chart responsive by passing in { responsive: true, maintainAspectRatio: false } to the options prop.

Next we replace the existing code in HomePage.vue with:

<template>
  <div class="page">
    <ValidationObserver ref="observer" v-slot="{ invalid }">
      <b-form [@submit](http://twitter.com/submit "Twitter profile for @submit").prevent="onSubmit" novalidate>
        <b-form-group label="Description">
          <ValidationProvider name="description" rules="required" v-slot="{ errors }">
            <b-form-input
              :state="errors.length == 0"
              v-model="form.description"
              type="text"
              required
              placeholder="Description"
              name="description"
            ></b-form-input>
            <b-form-invalid-feedback :state="errors.length == 0">Description is required</b-form-invalid-feedback>
          </ValidationProvider>
        </b-form-group>
        <b-form-group label="Amount">
          <ValidationProvider name="amount" rules="required|min_value:0" v-slot="{ errors }">
            <b-form-input
              :state="errors.length == 0"
              v-model="form.amount"
              type="text"
              required
              placeholder="Amount"
            ></b-form-input>
            <b-form-invalid-feedback :state="errors.length == 0">{{errors.join('. ')}}</b-form-invalid-feedback>
          </ValidationProvider>
        </b-form-group>
        <b-form-group label="Date">
          <ValidationProvider name="amount" rules="date" v-slot="{ errors }">
            <b-form-input
              :state="errors.length == 0"
              v-model="form.date"
              type="text"
              required
              placeholder="Date (YYYY/MM/DD)"
              name="date"
            ></b-form-input>
            <b-form-invalid-feedback :state="errors.length == 0">{{errors[0]}}</b-form-invalid-feedback>
          </ValidationProvider>
        </b-form-group>
        <b-button type="submit">Add</b-button>
      </b-form>
    </ValidationObserver>

    <b-table-simple responsive>
      <b-thead>
        <b-tr>
          <b-th sticky-column>Description</b-th>
          <b-th>Amount</b-th>
          <b-th>Date</b-th>
          <b-th>Delete</b-th>
        </b-tr>
      </b-thead>
      <b-tbody>
        <b-tr v-for="e in expenses" :key="e.id">
          <b-th sticky-column>{{e.description}}</b-th>
          <b-td>${{e.amount}}</b-td>
          <b-td>{{e.date | formatDate}}</b-td>
          <b-td>
            <b-button [@click](http://twitter.com/click "Twitter profile for @click")="deleteSingleExpense(e.id)">Delete</b-button>
          </b-td>
        </b-tr>
      </b-tbody>
    </b-table-simple>
  </div>
</template>

<script>
import "bootstrap/dist/css/bootstrap.css";
import "bootstrap-vue/dist/bootstrap-vue.css";
import { requestsMixin } from "../mixins/requestsMixin";

export default {
  name: "home",
  mixins: [requestsMixin],
  data() {
    return {
      form: {}
    };
  },
  beforeMount() {
    this.getAllExpenses();
  },
  computed: {
    expenses() {
      return this.$store.state.expenses;
    }
  },
  methods: {
    async onSubmit() {
      const isValid = await this.$refs.observer.validate();
      if (!isValid) {
        return;
      }
      await this.addExpense(this.form);
      await this.getAllExpenses();
    },

  async getAllExpenses() {
      const response = await this.getExpenses();
      this.$store.commit("setExpenses", response.data);
    },

  async deleteSingleExpense(id) {
      const response = await this.deleteExpense(id);
      await this.getAllExpenses();
    }
  }
};
</script>

We use the form and table from BootstrapVue to build the form and table. For form validation, we wrap ValidationProvider around each b-form-input to get form validation. The form validation are rules are registered in main.js so we can use them here.

We add :state=”errors.length == 0" in each b-form-input so that we get the right validation message displayed and styled properly for each input. The errors object has the form validation error messages for each input. We also need to specify the name prop in ValidationProvider and b-form-input so that form validation rules are applied to the input inside the ValidationProvider . We put the form inside the ValidationObserver component here to let us validate the whole form. With Vee-Validate, we get the this.$refs.observer.validate() function when we use ValidationObserver like we did in the code above. It returns a promise that resolves to true if the form is valid and false otherwise. So if it resolves to false, we don’t run the rest of the function’s code.

this.$store is provided by Vuex. We call commit on it to store the values into the Vuex store. We get the latest values from the store in the computed block.

Next in App.vue , replace the existing code with:

<template>
  <div id="app">
    <b-navbar toggleable="lg" type="dark" variant="info">
      <b-navbar-brand href="#">Expense Tracker</b-navbar-brand>

<b-navbar-toggle target="nav-collapse"></b-navbar-toggle>

<b-collapse id="nav-collapse" is-nav>
        <b-navbar-nav>
          <b-nav-item to="/" :active="path  == '/'">Home</b-nav-item>
          <b-nav-item to="/graph" :active="path  == '/graph'">Graph</b-nav-item>
        </b-navbar-nav>
      </b-collapse>
    </b-navbar>
    <router-view />
  </div>
</template>

<script>
export default {
  beforeMount() {
    window.Chart.defaults.global.defaultFontFamily = `
  -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
  "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
  sans-serif`;
  },
  data() {
    return {
      path: this.$route && this.$route.path
    };
  },
  watch: {
    $route(route) {
      this.path = route.path;
    }
  }
};
</script>

<style lang="scss">
.page {
  padding: 20px;
}
</style>

In this file, we add the BootstrapVue b-navbar to display a top bar. We watch for URL changes so that we can set the correct link to be active. In the data block, we set the initial route, so that we get the correct link highlighted when the app first loads.

Next in main.js , we replace the existing code with:

import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store";
import BootstrapVue from "bootstrap-vue";
import { ValidationProvider, extend, ValidationObserver } from "vee-validate";
import { required, min_value } from "vee-validate/dist/rules";
import { dateFilter } from "./filters/date";
import ExpenseChart from "./charts/ExpenseChart";extend("required", required);
extend("min_value", min_value);
extend("date", {
  validate: value =>
    /^(19|20)dd[/]([1-9]|0[1-9]|1[012])[/]([1-9]|0[1-9]|[12][0-9]|3[01])$/.test(
      value
    ),
  message: "Date must be in YYYY/MM/DD format"
});Vue.component("expense-chart", ExpenseChart);
Vue.filter("formatDate", dateFilter);
Vue.use(BootstrapVue);
Vue.component("ValidationProvider", ValidationProvider);
Vue.component("ValidationObserver", ValidationObserver);
Vue.config.productionTip = false;new Vue({
  router,
  store,
  render: h => h(App)
}).$mount("#app");

The validation rules we used in Home.vue are added here. Note that we can have custom validation rules like the date rule. It is easy to define custom rules with VeeValidate 3. We also register the ValidationProvider and ExpenseChart components so that we can use them in our app.

We register the ValidationObserver component here to let us validate the whole form in HomePage.vue .

Also, we add the form validation rules from Vee-Validate that we want to use here. We added the built in required and min_value rules that we used in HomePage.vue here and defined the date rule below the first 2 extend calls. The date rule specifies that we check the value for the YYYY/MM/DD format and also specified the error message if validation error is found.

In router.js , we replace the existing code with:

import Vue from 'vue'
import Router from 'vue-router'
import Home from './views/Home.vue'
import Graph from './views/Graph.vue'

Vue.use(Router)

export default new Router({
  mode: 'history',
  base: process.env.BASE_URL,
  routes: [
    {
      path: '/',
      name: 'home',
      component: Home
    },
    {
      path: '/graph',
      name: 'graph',
      component: Graph
    }
  ]
})

to include the Graph page that we created.

In store.js , we replace the code with:

import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    expenses: []
  },
  mutations: {
    setExpenses(state, payload) {
      state.expenses = payload;
    }
  },
  actions: {}
});

so that we can keep the expense data in the store whenever it’s obtained.

Finally in index.html , we change the existing code to:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width,initial-scale=1.0" />
    <link rel="icon" href="<%= BASE_URL %>favicon.ico" />
    <title>Expense Tracker</title>
  </head>
  <body>
    <noscript>
      <strong
        >We're sorry but frontend doesn't work properly without JavaScript
        enabled. Please enable it to continue.</strong
      >
    </noscript>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>

to change the title. We already imported the Bootstrap styles in App.vue so we don’t have to include it here.

After writing all that code, we can run our app. Before running anything, install nodemon by running npm i -g nodemon so that we don’t have to restart back end ourselves when files change.

Then run back end by running npm start in the backend folder and npm run servein the frontend folder.

Categories
Express JavaScript Nodejs

Basic Routing with Express

Routing is the most important part of a back end application. Express allows us to route URLs to our route handler code easily.

In this article, we’ll look at how to create basic routes with Express.

Basic Routing

Routing is where an Express application response to a client request from a URL or path and a specific HTTP request method, like GET or POST.

Each route in Express can have one more handler function, which are executed when the route is matched.

The general definition of a route takes the following format:

app.METHOD(PATH, HANDLER);

app is the Express app instance.

The METHOD is an HTTP request method in lowercase. Possible methods include GET, POST, PUT, and DELETE.

PATH is the path for the route. HANDLER is the handler function that’s run when the route is matched.

For example, we can write:

app.get('/', (req, res) => {  
  res.send('Hello World!')  
})

To display 'Hello World!' on the screen.

If we want our app to accept a POST request, we can use app.post as follows:

app.post('/', (req, res) => {  
  res.send('Received POST request');  
})

We can test this with an HTTP client like Postman by send a POST request to the URL that our app is running on. Then we should get:

Received POST request

In the response body.

Likewise, we can do the same for PUT and DELETE requests as follows:

app.put('/', (req, res) => {  
  res.send('Got a PUT request at /user')  
})

app.delete('/', (req, res) => {  
  res.send('Got a DELETE request')  
})

Notice that in each route handler, we have a req and res parameter. The req has the request object which has the URL, headers and other fields.

The res object lets us render a response back to the client-side.

Request Object

The req parameter we have in the route handlers above is the req object.

It has some properties that we can use to get data about the request that’s made from the client-side. The more important ones are listed below.

req.baseUrl

The req.baseUrl property holds the base URL of the router instance that’s mounted.

For example, if we have:

const express = require('express');
const app = express();  
const greet = express.Router();  
greet.get('/', (req, res) => {  
  console.log(req.baseUrl);  
  res.send('Hello World');  
})

app.use('/greet', greet);

app.listen(3000, () => console.log('server started'));

Then we get /greet from the console.log .

req.body

req.body has the request body. We can parse JSON bodies with express.json() and URL encoded requests with express.urlencoded() .

For example, if we have:

const express = require('express')  
const app = express()
app.use(express.json())  
app.use(express.urlencoded({ extended: true }))
app.post('/', (req, res) => {  
  res.json(req.body)  
})

app.listen(3000, () => console.log('server started'));

Then when we make a POST request with a JSON body, then we get back the same that we sent in the request.

req.cookies

We can get cookies that are sent by the request with the req.cookies property.

req.hostname

We can get the hostname from the HTTP header with req.hostname .

When the trust proxy setting doesn’t evaluate to false , then Express will get the value from the X-Forwarded-Host header field. The header can be set by the client or by the proxy.

If there’s more than one X-Forwarded-Host header, then the first one will be used.

For example, if we have:

const express = require('express')  
const app = express()
app.use(express.json())  
app.use(express.urlencoded({ extended: true }))

app.get('/', (req, res) => {  
  res.json(req.hostname)  
})

app.listen(3000, () => console.log('server started'));

Then we get the domain name that the app is hosted in if there’re no X-Forwarded-Host headers and trust proxy doesn’t evaluate to false .

req.ip

We can get the IP address that the request is made from with this property.

req.method

The method property has the request method of the request, like GET, POST, PUT or DELETE.

req.params

params property has the request parameters from the URL.

For example, if we have:

const express = require('express')  
const app = express()
app.use(express.json())  
app.use(express.urlencoded({ extended: true }))

app.get('/:name/:age', (req, res) => {  
  res.json(req.params)  
})

app.listen(3000, () => console.log('server started'));

Then when we pass in /john/1 as the parameter part of the URL, then we get:

{  
    "name": "john",  
    "age": "1"  
}

as the response from the route above.

req.query

The query property gets us the query string from the request URL parsed into an object.

For example, if we have:

const express = require('express')  
const app = express()
app.use(express.json())  
app.use(express.urlencoded({ extended: true }))

app.get('/', (req, res) => {  
  res.json(req.query)  
})

app.listen(3000, () => console.log('server started'));

Then when we append ?name=john&age=1 to the end of the hostname, then we get back:

{  
    "name": "john",  
    "age": "1"  
}

from the response.

Response Object

The response object has some useful methods to let us return various kinds of responses.

res.append

The append method lets us attach response headers to our responses.

For example, if we have the following code:

const express = require('express')  
const app = express()
app.use(express.json())  
app.use(express.urlencoded({ extended: true }))

app.get('/', (req, res) => {  
  res.append('Link', 'http://localhost/', 'http://localhost:3000'])  
  res.append('Set-Cookie', 'foo=bar; Path=/; HttpOnly')  
  res.append('Warning', 'Alert')  
  res.send();  
})

app.listen(3000, () => console.log('server started'));

Then when we go to Postman, we should see the same data returned in the Headers tab of the response when we look at the data.

Note that we have to run res.send() to actually send the response.

res.attachment

res.attachment let us add a file to the response. It doesn’t send the response.

For example, we can use it as follows:

const express = require('express')  
const app = express()
app.use(express.json())  
app.use(express.urlencoded({ extended: true }))
app.get('/', (req, res) => {  
  res.attachment('../public/foo.txt');  
  res.send();  
})

app.listen(3000, () => console.log('server started'));

Then if we have a foo.txt in the public folder, then the file will be downloaded if we make the request to the route.

Note that again we have res.send() to actually send the response.

res.cookie

res.cookie lets us add a cookie to the response.

For example, if we have:

const express = require('express')  
const app = express()
app.use(express.json())  
app.use(express.urlencoded({ extended: true }))

app.get('/', (req, res) => {  
  res.cookie('name', 'foo', { domain: 'repl.it', path: '/', secure: true })  
  res.send();  
})

app.listen(3000, () => console.log('server started'));

Then we send a cookie with name foo to the client. We can check in Postman under the Cookies link in the top right corner.

res.download

res.download sends a file response to the server.

For example, if we have:

const express = require('express')  
const app = express()
app.use(express.json())  
app.use(express.urlencoded({ extended: true }))
app.get('/', (req, res) => {  
  res.download('./public/foo.txt');  
})
app.listen(3000, () => console.log('server started'));

Then when a request is made to this route, then we’ll get a file downloaded.

res.json

res.json lets us send a JSON response to the client. The parameter can be any JSON type, including object, array, string, Boolean, number, or null.

For example, if we have:

const express = require('express')  
const app = express()
app.use(express.json())  
app.use(express.urlencoded({ extended: true }))

app.get('/', (req, res) => {  
  res.json({ message: 'hi' });  
})

app.listen(3000, () => console.log('server started'));

Then we get:

{"message":"hi"}

as the response.

res.redirect

We can use this to redirect to another URL with the string passed in. For example, if we have:

const express = require('express')  
const app = express()
app.use(express.json())  
app.use(express.urlencoded({ extended: true }))
app.get('/', (req, res) => {  
  res.redirect('http://medium.com'));  
})

app.listen(3000, () => console.log('server started'));

Then we’ll see the content of http://medium.com when we make the request to the route above.

res.status

res.status lets us send a status code response. We can use it with the end , send , or sendFile methods by calling them after calling status .

For example, if we have:

const express = require('express')  
const app = express()app.use(express.json())  
app.use(express.urlencoded({ extended: true }))

app.get('/', (req, res) => {  
  res.status(403).end();  
})
app.listen(3000, () => console.log('server started'));

Then we get a 403 response.

Conclusion

Adding routes is simple with Express. We just have to tell it the URL and method to listen for, and the route handler to handle requests that match them.

We can get query strings and URL parameters with the Request object.

Then we can send a status, text, or file according to our preference with the Response object.

Categories
JavaScript Testing

Introduction to Testing with Jest

With apps being as complex as they’re today, we need some way to verify that our changes to apps didn’t cause any regressions. To do this, we need unit tests.

We can add unit tests to JavaScript apps easily with the Jest test runner. It’s very easy to get running and we can write lots of tests with it that runs quickly.

In this article, we’ll look at how to set up Jest from scratch and write some tests with it. Both synchronous an asynchronous functions will be tested.

Getting Started

To get started, we simply run a few commands. In our app’s project folder, we run:

yarn add --dev jest

or

npm install --save-dev jest

to add Jest to our JavaScript apps.

The example we have below will have 2 simple scripts each containing a module that has several functions.

First we create the code that we’re testing. We’ll test both code that doesn’t talk to the Internet and talk that does.

We create example.js and put in the following code:

const add = (a, b) => a + b;  
const identity = a => a;  
const deepCopy = (obj, copiedObj) => {  
    if (!copiedObj) {  
        copiedObj = {};  
    }  
    for (let prop of Object.keys(obj)) {  
        copiedObj = {  
            ...copiedObj,  
            ...obj  
        };  
        if (typeof obj[prop] === 'object' && !copiedObj[prop]) {  
            copiedObj = {  
                ...copiedObj,  
                ...obj[prop]  
            };  
            deepCopy(obj[prop], copiedObj);  
        }  
    }  
    return copiedObj;  
}  
module.exports = {  
    add,  
    identity,  
    deepCopy  
}

The code above just have functions to add numbers and a function that returns the same thing that it passes in.

Then we create request.js with the following:

const fetchJokes = async () => {  
    const response = await fetch('[http://api.icndb.com/jokes/random/'](http://api.icndb.com/jokes/random/'));  
    return response.json();  
};  
module.exports = {  
    fetchJokes  
}

The code above gets random jokes from the Chuck Norris Jokes API.

Writing Tests for Synchronous Code

Now that we have the code that we want to test, we’re ready to create some test code for them.

First we create tests for the functions in example.js .

We add the following tests:

const { add, identity } = require('./example');

test('adds 1 + 2 to equal 3', () => {  
    expect(add(1, 2)).toBe(3);  
});

test('adds 1 + 2 is truthy', () => {  
    const sum = add(1, 2);  
    expect(sum).toBeTruthy();  
});

test('adds 1 + 2 to be defined', () => {  
    const sum = add(1, 2);  
    expect(sum).not.toBeUndefined();  
    expect(sum).toBeDefined();  
});

test('identity(null) to be falsy', () => {  
    expect(identity(null)).toBeFalsy();  
    expect(identity(null)).not.toBeTruthy();  
});

The code above is very simple. It imports the functions from example.js and run some tests with it.

The first test calls add from example.js by passing in 2 numbers and checking that the returned result is what we expect.

The 2 tests below it are very similar except that we use different matcher functions to check the results.

The last test runs the identity function with null and they use the toBeFalsy and not.toBeTruthy matchers to check that null is indeed falsy.

In summary, Jest has the following matchers:

  • toBeNull — matches only null
  • toBeUndefined — matches only undefined
  • toBeDefined — is the opposite of toBeUndefined
  • toBeTruthy — matches anything truthy
  • toBeFalsy — matches anything falsy
  • toBeGreaterThan — check if a value is bigger than what we expect
  • toBeGreaterThanOrEqual — check if a value is bigger than or equal to what we expect
  • toBeLessThan — check if a value is less than what we expect
  • toBeLessThanOrEqual — check if a value is less than or equal what we expect
  • toBe — check if a value is the same using Object.is to compare the values
  • toEqual — checks every field recursively of 2 objects to see if they’re the same
  • toBeCloseTo — check for floating point equality of values.
  • toMatch — check if a string matches a give regex
  • toContain — check if an array has the given value
  • toThrow — check if an exception is thrown

The full list of expect methods are here.

We can use some of them as follows by adding them to example.test.js:

test('but there is a "foo" in foobar', () => {  
    expect('foobar').toMatch(/foo/);  
});

test(  
    'deep copies an object and returns an object with the same properties and values',  
    () => {  
        const obj = {  
            foo: {  
                bar: {  
                    bar: 1  
                },  
                a: 2  
            }  
        };  
        expect(deepCopy(obj)).toEqual(obj);  
    }  
);

In the code above, we use the toMatch matcher to check if 'foobar' has the 'foo' substring in the first test.

In the second test, we run our deepCopy function that we added earlier and test if it actually copies the structure of an object recursively with the toEqual matcher.

toEqual is very handy since it checks for deep equality by inspecting everything in the object recursively rather than just for reference equality.

Writing Tests for Asynchronous and HTTP Request Code

Writing tests for HTTP tests requires some thinking. We want the tests to run anywhere with or without an Internet connection. Also the test shouldn’t depend on the results of the live server since it’s supposed to be self contained.

This means that we have to mock the HTTP request rather than calling our code directly.

To test our fetchJokes function in request.js , we have to mock the function.

To do this, we create a __mocks__ folder in our project folder and in it, create requests.js . The file name should always match the filename with the code that we’re mocking.

We can mock it as follows:

const fetchJokes = async () => {  
    const mockResponse = {  
        "type": "success",  
        "value": {  
            "id": 407,  
            "joke": "Chuck Norris originally wrote the first dictionary. The definition for each word is as follows - A swift roundhouse kick to the face.",  
            "categories": []  
        }  
    };  
    return Promise.resolve(mockResponse);  
};  
module.exports = {  
    fetchJokes  
}

Both the real and mock fetchJokes function returns a promise. The real function returns a promise from the fetch function while the mock one calls Promise.resolve directly.

This means that the real one actually makes the GET request and the mock ones resolves to the response that we want to test for.

Then in example.test.js , we add:

jest.mock('./request');  
const { fetchJokes } = require('./request');

to the top of the file and:

test('jokes to be fetched', async () => {  
    const mockResponse = {  
        "type": "success",  
        "value": {  
            "id": 407,  
            "joke": "Chuck Norris originally wrote the first dictionary. The definition for each word is as follows - A swift roundhouse kick to the face.",  
            "categories": []  
        }  
    };  
    await expect(fetchJokes()).resolves.toEqual(mockResponse)  
});

to the bottom of it.

The test above just call the mock fetchJokes function since we have:

jest.mock('./request');

However, we still need to import the real one since the real one is replaced with the mock one when the test runs.

resolves will resolve the promise returned from the mock fetchJokes function and toEqual will check the structure of the response from the promise’s resolved value.

Finally, in the scripts section of package.json , we put:

"test": "jest"

so we can run npm test to run the tests.

Then when we run npm test , we should get:

PASS  ./example.test.js  
  √ adds 1 + 2 to equal 3 (2ms)  
  √ adds 1 + 2 is truthy  
  √ adds 1 + 2 to be defined (1ms)  
  √ identity(null) to be falsy  
  √ but there is a "foo" in foobar  
  √ deep copies an object and returns an object with the same properties and values (1ms)  
  √ jokes to be fetched (1ms)Test Suites: 1 passed, 1 total  
Tests:       7 passed, 7 total  
Snapshots:   0 total  
Time:        1.812s, estimated 2s  
Ran all test suites.

No matter how and when we run the tests, they should still pass since they’re independent of each other and don’t rely on external network requests.

When we write unit tests, we have to make sure each test is independent from each other and also should make any network requests.

Jest makes testing easy by letting mock things like code that makes HTTP requests easily.

It’s also easy to to set up, and has lots of matchers for all kinds of tests.

Categories
JavaScript Nodejs

Node.js’ fs Module — Getting File Information

Manipulating files and directories are basic operations for any program. Since Node.js is a server-side platform and can interact with the computer that it’s running on directly, being able to manipulate files is a basic feature.

Fortunately, Node.js has an fs module built into its library. It has many functions that can help with manipulating files and folders. File and directory operations that are supported include basic ones like manipulating and opening files in directories.

Likewise, it can do the same for files. It can do this both synchronously and asynchronously. It has an asynchronous API that has functions that support promises. Also, it can show statistics for a file.

Almost all the file operations that we can think of can be done with the built-in fs module.

In this article, we will use the functions in the fs module to get data about a file located in the fs.Stats object with the fs.stat(), fs.lstat(), and fs.fstat() functions.

To get information about a file, we can use the fs.Stats object, which is returned by the fs.stat(), fs.lstat(), and fs.fstat() functions, and their synchronous counterparts.

They can display numeric data as bigint as it’s passed in as an option key with its value set to true. It has nanosecond-precision properties suffixed with Ns.

The stat function takes a path object which can be a string, Buffer, or a URL object as the first argument.

A second argument is an object that can take the bigint as the key, which is a boolean value. If it’s set to true, then numerical information will be returned as bigInts.

The third argument is a callback function that has the error object for the first parameter and the stats object as the second parameter, which has the information about a file and it’s running when the file information is retrieved.

The stat function runs asynchronously. Its synchronous counterpart is the statSync function, which takes the same first two arguments without the callback function. statSync returns the file information as an object.

lstat is similar to stat, but it doesn’t follow the symbolic link. It takes a path object which can be a string, Buffer, or a URL object as the first argument.

A second argument is an object which can take the bigint as the key, which is a boolean value. If it’s set to true, then numerical information will be returned as bigInts.

The third argument is a callback function that has the error object for the first parameter and the stats object as the second parameter, which has the information about a file and it’s running when the file information is retrieved.

When the path that’s passed in is a symbolic link, then it gives the information about the symbolic link. lstat runs asynchronously, so that the data is retrieved in an indeterminate amount of time.

Its synchronous counterpart, the lstatSync function, takes the same arguments as the lstat function without the callback function and returns the Stat object which has the file information.

The fstat function is similar to the stat function. It takes a path object which can be a string, Buffer, or an URL object as the first argument.

The second argument is an object which can take the bigint as the key, which is a boolean value. If it’s set to true, then numerical information will be returned as bigInts.

The third argument is a callback function that has the error object for the first parameter and the stats object as the second parameter, which has the information about a file and it’s running when the file information is retrieved.

The only difference between stat and fstat is that it takes a file descriptor instead of a path object.

We can get the file descriptor from the callback that’s accepted by the fs.open function and its promise and synchronous counterparts, fsPromises.open, and fs.opensync.

To use the fs.stat function, we can use it like in the following code:

const fs = require("fs");
fs.stat("./files/file.txt", (err, stat) => {  
  if (err) throw err;  
  console.log(stat);  
});

Then, if we run the code above, we get something like the following output:

Stats {  
  dev: 3605029386,  
  mode: 33206,  
  nlink: 1,  
  uid: 0,  
  gid: 0,  
  rdev: 0,  
  blksize: 4096,  
  ino: 22799473115106240,  
  size: 0,  
  blocks: 0,  
  atimeMs: 1572569358035.625,  
  mtimeMs: 1572569358035.625,  
  ctimeMs: 1572569358035.625,  
  birthtimeMs: 1572569358035.625,  
  atime: 2019-11-01T00:49:18.036Z,  
  mtime: 2019-11-01T00:49:18.036Z,  
  ctime: 2019-11-01T00:49:18.036Z,  
  birthtime: 2019-11-01T00:49:18.036Z  
}

As we can see, the Stats object has many properties. The data properties are listed above. It also has a few function properties.

The data properties in the Stats object means the following:

  • dev — The numeric identifier of the device storing the given file. It can be a number or a bigInt.
  • ino — The “inode” number of the file. It’s a number that contains basic information about a file, directory, or other file system object. It can be a number or a bigInt.
  • mode — Bit-field description of the file type and mode. It can be a number or a bigInt.
  • nlink — Number of hard links that exist for the file. It can be a number or a bigInt.
  • uid — The numeric user identifier of the user that owns the file. Applicable to POSIX systems only. It can be a number or a bigInt.
  • gid — The numeric group identifier of the user that owns the file. Applicable to POSIX systems only. It can be a number or a bigInt.
  • rdev — Numeric device identifier of the file if it’s a special file. A file is special if it’s used for I/O. For example, page files and hibernation files are considered special files. It can be a number or a bigInt.
  • size — The size of the file in bytes. It can be a number or a bigInt.
  • blksize — The block size for a file system I/O. It can be a number or a bigInt.
  • blocks — The number of blocks allocated to the file. It can be a number or a bigInt.
  • atimeNs — The timestamp indicating when the file was last accessed in nanoseconds since the POSIX Epoch, which is the time relative to January 1, 1970 midnight. It can be a number or a bigInt.
  • mtimeNs — The timestamp indicating when the file was last modified in nanoseconds since the POSIX Epoch, which is the time relative to January 1, 1970 midnight. It can be a number or a bigInt.
  • ctimeNs — The timestamp indicating when the file was last changed in nanoseconds since the POSIX Epoch, which is the time relative to January 1, 1970 midnight. It can be a number or a bigInt.
  • birthtimeNs — The timestamp indicating when the file was created in nanoseconds since the POSIX Epoch, which is the time relative to January 1, 1970 midnight. It can be a number or a bigInt.
  • atime — The timestamp indicating when the file was last accessed in milliseconds since the POSIX Epoch, which is the time relative to January 1, 1970 midnight. It can be a number or a bigInt.
  • mtime — The timestamp indicating when the file was last modified in milliseconds since the POSIX Epoch, which is the time relative to January 1, 1970 midnight. It can be a number or a bigInt.
  • ctime — The timestamp indicating when the file was last changed in milliseconds since the POSIX Epoch, which is the time relative to January 1, 1970 midnight. It can be a number or a bigInt.
  • birthtime — The timestamp indicating when the file was created in milliseconds since the POSIX Epoch, which is the time relative to January 1, 1970 midnight. It can be a number or a bigInt.

The Stats object also has the following function properties to check for the basic information about a file:

  • isBlockDevice() — This is a function with a boolean return value that returns true if the file is a block device. A block device refers to a file that represents the device that stores files in blocks and also retrieves them as such.
  • isCharacterDevice() — This is a function with a boolean return value that returns true if the file is a character device. A character device refers to a file that represents the device that provided unbuffered, direct access to the hardware device. They don’t have to allow programs to read or write a single character at a time.
  • isDirectory() — This is a function with a boolean return value that returns true if the item is a directory.
  • isFIFO() — This is a function with a boolean return value that returns true if the item is a first-in-first-out pipe. FIFO pipe means that the first bits of a file going into the device will be the same ones that come out when retrieved. It only allows for unidirectional communication.
  • isFile() — This is a function with a boolean return value that returns true if the item is a file.
  • isSocket() — This is a function with a boolean return value that returns true if the item is a socket. A socket is a special file that enables communication between two processes. It can send data and file descriptors across a domain socket. It can do bidirectional communication.
  • isSymbolicLink() — This is a function with a boolean return value that returns true if the item is a symbolic link. A symbolic link is a reference to another file or directory in the form of an absolute or relative path.

To use the synchronous version of the stat function, the statSync function, we can write something like the following code:

const fs = require("fs");
const stat = fs.statSync("./files/file.txt");  
console.log(stat);

The Stat object is returned directly from the statSync function.

To use the fstat function, we have to get the file descriptor first, which we can do with the open function and its variants. For example, if we want to use the open function to get the file descriptor, we can write the following:

const fs = require("fs");
fs.open("./files/file.txt", "r", (err, fd) => {  
  if (err) throw err;  
  fs.fstat(fd, (err, stat) => {  
    if (err) throw err;  
    console.log(stat);  
    fs.close(fd, err => {  
      if (err) throw err;  
    });  
  });  
});

We can use fstat with the promise version of the open function like in the following code:

const fsPromises = require("fs").promises;  
const fs = require("fs");
(async () => {  
  const fdObj = await fsPromises.open("./files/file.txt", "r");  
  fs.fstat(fdObj.fd, (err, stat) => {  
    if (err) throw err;  
    console.log(stat);  
    fs.close(fdObj.fd, err => {  
      if (err) throw err;  
    });  
  });  
})();

The promise version of the open function returns a promise that resolves to an object with the file descriptor inside it. We can use the fd property to get the file descriptor and pass it into the fstat function.

Likewise, with the lstat function, we can call it as in the following code:

const fs = require("fs");
fs.lstat("./files/file.txt", (err, stat) => {  
  if (err) throw err;  
  console.log(stat);  
});

The lstat does almost everything as stat does, except that it gets the data of a symbolic link instead of following it, so we will get output similar to the ones above.

The fs.stat(), fs.lstat(), and fs.fstat() functions are very useful for getting data about files, directories, and symbolic links.

They can get us the Stat object which has lots of information that we can use, including access time, modified time, whether the file is a special file, the device it’s stored on, and many other pieces of information through its value and function properties.

Data can be displayed as numbers or bigInt if they’re numerical and timestamps are available in both milliseconds and nanoseconds for extra precision.