April 10, 2020 - coding javascript dapr
Dapr - Creating a User Login/Register Microservice

Xavier Geerinck
In an earlier post, I went in more detail how you can utilize Dapr to create a Microservice that communicates over gRPC for quick Inter-Process Communication (IPC).
Now I would like to take this a step further and create a real microservice that allows us to perform account management.
Pre-Requisites
- Dapr installed locally (check with
dapr --version
)- Note: you can update with powershell -Command "iwr -useb https://raw.githubusercontent.com/dapr/cli/master/install/install.ps1 | iex"
- Node.js
- CosmosDB
- A Typescript Project as explained in my earlier post here
Note: You can update your Dapr version through one of the following
- Linux:
wget -q https://raw.githubusercontent.com/dapr/cli/master/install/install.sh -O - | /bin/bash
- PowerShell:
powershell -Command "iwr -useb https://raw.githubusercontent.com/dapr/cli/master/install/install.ps1 | iex"
Project Scope / Requirements
Scope: Allow us to create users and let them login, returning a JWT token.
Routes:
/user/login
should return a JWT token/user/register
should create an account
Writing a Hello World route in Express and Typescript
Since Dapr takes care of state management, we are going to write a Microservice that automatically hooks up to this state management (in this case creating and finding our users). But first we have to install our dependencies, so let's install those:
# Dependencies Installationnpm i -S express body-parser isomorphic-fetch express-validator bcrypt uuidnpm i @types/express @types/uuid @types/bcrypt @types/isomorphic-fetch @types/express-validator --save-dev
Whereafter we can add the following code which spins up the server and initializes the route /
that will respond with Hello World
.
import express = require('express');const PORT = process.env.APP_HTTP_PORT || 9000;const PORT_DAPR = process.env.DAPR_HTTP_PORT || 9500;const app: express.Application = express();app.get('/', (req, res) => res.send('Hello World'));app.listen(PORT, () => console.log(`Server started at 0.0.0.0:${PORT}, Dapr at ${PORT_DAPR}`));
Starting this with Dapr on the app port 9000
can be done with:
dapr run --app-id node-api-user --app-port 9000 --port 9500 npm run start
Note: Port
9500
will be used for calling Dapr (e.g. state management)
Dapr State Management
Setting up connection to CosmosDB
For our State Management backend we will utilize CosmosDB
. To set this up we can refer to the documentation which explains that we should adapt our local created components/statestore.yaml
file. When doing this, we end up with this:
apiVersion: dapr.io/v1alpha1kind: Componentmetadata:name: state-cosmosdbspec:type: state.azure.cosmosdbmetadata:- name: urlvalue: <YOUR_URL>- name: masterKeyvalue: <YOUR_PRIMARY_KEY>- name: databasevalue: <YOUR_DB_NAME>- name: collectionvalue: <YOUR_COLLECTION_NAME>
Note: For use in production you can create a
.yaml
file (e.g.cosmos.yaml
) and deploy it withkubectl apply -f cosmos.yaml
After doing this, we are now able to access our state through the DAPR port which we selected during start-up. We can access this through the environment variable DAPR_HTTP_PORT
.
Example State Call
We can now store and receive states through http requests. To do this I create a sample.http
file which includes the lines I want to send. When then installing the VSCode Extension: REST Client, I am able to utilize CTRL + ALT + R or CTRL + SHIFT + P; > Rest Client: Send Request to send a request.

My sample.http
file looks like this:
POST http://localhost:9500/v1.0/state/state-cosmosdb[{"id": "e86f44ee-ed09-4a71-afd7-3d6bbf782b39","name": "Xavier Geerinck"}]###GET http://localhost:9500/v1.0/state/state-cosmosdb/e86f44ee-ed09-4a71-afd7-3d6bbf782b39
Whereafter when Running the 2 queries we get the following results (CTRL + ALT + R):


Writing our API Routes with Express
We can now write our API routes with the concepts learned above. When we implement this, we get the following end-result:
import express = require('express');import { v4 as uuidv4 } from 'uuid';const bodyParser = require('body-parser');const { check, validationResult } = require('express-validator');const bcrypt = require('bcrypt');const fetch = require('isomorphic-fetch');const PORT = process.env.APP_HTTP_PORT || 9000;const PORT_DAPR = process.env.DAPR_HTTP_PORT || 9500;const DAPR_URL = `http://localhost:${PORT_DAPR}/v1.0`;const DAPR_URL_STATE = `${DAPR_URL}/state/state-cosmosdb`;const app: express.Application = express();app.use(bodyParser.json());app.listen(PORT, () => console.log(`Server started at 0.0.0.0:${PORT}, Dapr at ${PORT_DAPR}`));app.post('/user/register', [check('firstName').isAlpha(),check('lastName').isAlpha(),check('email').isEmail(),check('password').isLength({ min: 6 })], async (req: express.Request, res: express.Response) => {const errors = validationResult(req);if (!errors.isEmpty()) {return res.status(422).json({ errors: errors.array() });}try {// First encrypt passwordconst bcryptSalt = await bcrypt.genSalt(10);req.body.password = await bcrypt.hash(req.body.password, bcryptSalt);// Save Userconst userId = uuidv4();console.log(`Creating user with Id: ${userId}`);const fetchRes = await fetch(`${DAPR_URL_STATE}`, {method: 'POST',body: JSON.stringify([{key: req.body.email,value: {id: userId,...req.body}}]),headers: {"Content-Type": "application/json"}});// Handle Responseif (!fetchRes.ok) {throw "Failed to register user";}return res.status(200).send({ success: true, user: {id: userId,...req.body}});} catch (e) {return res.status(500).send({ message: e });}});app.post('/user/login', [check('email').isEmail(),check('password').isLength({ min: 6 })], async (req: express.Request, res: express.Response) => {const errors = validationResult(req);if (!errors.isEmpty()) {return res.status(422).json({ errors: errors.array() });}try {// Find existing userconst fetchRes = await fetch(`${DAPR_URL_STATE}/${req.body.email}`);// Handle Responseif (!fetchRes.ok) {throw "Failed to find user";}const fetchResUser = await fetchRes.json();// Check passwordconst bcryptResult = await bcrypt.compare(req.body.password, fetchResUser.password);if (!bcryptResult) {throw "Incorrect password";}return res.status(200).send(fetchResUser);} catch (e) {return res.status(500).send({ message: e });}});
