A microservice is a self-contained unit that, together with other microservices, makes up a large application. By breaking applications into smaller units, each unit can be independently deployed and extended, developed by different teams in different programming languages, and tested independently.
Micro is a tiny (about 100 lines of code) module that makes it easy and fun to write microservices in Node.js. It’s easy to use and very fast. Whether you’ve used Node.js before or not, you’ll be ready to write your own micro-service after reading this article!
Started to prepare
To get started, there are only two small steps, starting with installing micro:
npm install -g micro
Copy the code
Global installation is selected here to ensure that we can use the micro command. If you know how to use NPM scripts, feel free to use them.
The second step is to create a new file called index.js to store the microservice:
touch index.js
Copy the code
The initial step
The index.js file needs to export a function to which Micro passes the connected request and response objects:
Module. exports = function (request, response) {// exports = function (request, response)}
Copy the code
The main method we use with Micro is send, which is used to send a response to a client. We require it first and send a simple “Hello World”, whatever the request is:
const { send } = require('micro')module.exports = function (request, response) { send(response, 200, 'Hello World! 👋 ')}
Copy the code
Send the first parameter is the response to be sent, the second parameter is the HTTP status code, and the third parameter is the response content (which can be JOSN).
To start microservices, you only need one command:
$ micro index.js Ready! Listening on http://0.0.0.0:3000
Copy the code
Open the page in your browser and you’ll see:
Do something useful
It’s a little boring, let’s do something useful! We want to make a microservice that keeps track of how many times a given path is requested. That is, /foo returns 1 the first time it is requested, 2 the next time, and so on.
We first need to know the pathname of the requested URL. Get the URL from request.url and parse it using the URL module of the Node.js core library (no additional installation required).
Import the URL module and use it to get the pathname from the URL resolution:
const { send } = require('micro')const url = require('url')module.exports = function (request, response) { const { pathname } = url.parse(request.url) console.log(pathname) send(response, 200, 'Hello World! 👋 ')}
Copy the code
Restart the microservice (press CTRL+C, then type Micro index.js again) and try. A request for localhost:3000/foo will print /foo on the console and a request for localhost:3000/bar will print /bar.
With the pathName, the last step is to save the number of times the path has been requested. Create a global object visits to save all access records:
const { send } = require('micro')const url = require('url')const visits = {}module.exports = function (request, response) { const { pathname } = url.parse(request.url) send(response, 200, 'Hello World! 👋 ')}
Copy the code
Check whether visits[pathName] exists at each request arrival. If so, increment the number of accesses and return the result to the client. Otherwise, set it to 1 and return it to the client.
const { send } = require('micro')const url = require('url')const visits = {}module.exports = function (request, response) { const { pathname } = url.parse(request.url) if (visits[pathname]) { visits[pathname] = visits[pathname] + 1 } else { visits[pathname] = 1 } send(response, 200, `This page has ${visits[pathname]} visits! `)}
Copy the code
Restart the service again, open localhost:3000/foo in your browser and refresh several times. You will see:
This is basically how I built Micro-Analytics in a matter of hours. The core concept is the same, just with a few more features. Once you figure out what you’re doing, the implementation code is pretty simple.
Persistent data
As you may have noticed, every time we restart the service, the data is deleted. We don’t store access data in the database, just in memory. Let’s fix this!
We will persist data using Level, which is a file-based key-value store. Micro has built-in support for async/await, which makes asynchronous code more elegant. The problem is that levels are based on callback functions, not promises. 😕
As usual, NPM has the modules we need. Forbes Lindesay developed then-LevelUp, which allows us to use levels through promise. If you don’t quite understand it, don’t worry, you’ll soon know what it is!
Install these modules first:
npm install level then-levelup
Copy the code
To create the database, we first import level and then specify where the database will be stored in JSON format. We use the then-Levelup exported method promisify to wrap the database and then export an async function instead of a normal function to use the await keyword:
const { send } = require('micro')const url = require('url')const level = require('level')const promisify = require('then-levelup')const db = promisify(level('visits.db', { valueEncoding: 'json'}))module.exports = async function (request, response) { /* ... * /}
Copy the code
Db.put (key, value) to save data (equivalent to visits[pathname] = x) and db.get(key) to retrieve data (equivalent to const x = visits[pathname]).
First, we want to know if there is a record of access to this path in the database. Do this with db.get(pathName) and wait for completion with the await keyword:
module.exports = async function (request, response) { const { pathname } = url.parse(request.url) const currentVisits = await db.get(pathname)}
Copy the code
If no await is added, currentVisits is assigned as a Promise, the function continues, and we don’t get the value returned from the database — which is not what we want!
In contrast, db.get raises a “NotFoundError” exception if there is no current access record. We’ll catch it with a try/catch block and set the initial value to 1 with db.put:
/* ... */module.exports = async function (request, response) { const { pathname } = url.parse(request.url) try { const currentVisits = await db.get(pathname) } catch (error) { if (error.notFound) await db.put(pathname, 1) }}
Copy the code
Continue to complete it, if there are already access records, we need to increase the number of accesses and send a response:
/ *... */module.exports = async function (request, response) { const { pathname } = url.parse(request.url) try { const currentVisits = await db.get(pathname) await db.put(pathname, currentVisits + 1) } catch (error) { if (error.notFound) await db.put(pathname, 1) } send(response, 200, `This page has ${await db.get(pathname)} visits! `)}
Copy the code
That’s all we have to do! The page access record is now persisted in the vists.db file and the service restart is not affected. Try restarting the service by opening localhost:3000/foo a few times, then restarting the service again and visiting the same page again. You will find that the previous number of accesses is still there, even though the service has been restarted.
Congratulations on building a page counter in 10 minutes! 🎉
This is the power of node.js’s small, centralized modules. Instead of messing around with basic components, we just focus on application development.