Data serialization

If you walk through the npm repository, you might see a lot of packages that do data serialization. This section uses a library called js2xmlparser to show you various possibilities, such as providing a way to encode data—not only as JSON, but also as XML. 

Remember that you can decide what flavor of framework you want to use.

To keep enriching our order-api application, imagine that you have a new request to provide support for XML encoding of request and response bodies as well. You may not want to duplicate the routes and controller; first, because it is not the best solution, and second, because you don't want to. One of the easiest ways to implement it is by using external packages such as js2xmlparser, as we mentioned previously. 

To use js2xmlparser, install the package for it in the order-api application with the following command:

$ npm install js2xmlparser --save
js2xmlparser is available at https://www.npmjs.com/package/js2xmlparser.

After that, your package.json file should look as follows:

{
"name": "order-api",
"version": "1.0.0",
"description": "This is the example from the Book Hands on RESTful Web Services with TypeScript 3",
"main": "./dist/server.js",
"scripts": {
"build": "npm run clean && tsc",
"clean": "rimraf dist && rimraf reports",
"lint": "tslint ./src/**/*.ts ./test/**/*.spec.ts",
"lint:fix": "tslint --fix ./src/**/*.ts ./test/**/*.spec.ts -t verbose",
"pretest": "cross-env NODE_ENV=test npm run build && npm run lint",
"test": "cross-env NODE_ENV=test mocha --reporter spec --compilers ts:ts-node/register test/**/*.spec.ts ",
"test:mutation": "stryker run",
"stryker:init": "stryker init",
"dev": "cross-env PORT=3000 NODE_ENV=dev ts-node ./src/server.ts",
"prod": "PORT=3000 npm run build && npm run start",
"tsc": "tsc"
},
"engines": {
"node": ">=8.0.0"
},
"keywords": [
"order POC",
"Hands on RESTful Web Services with TypeScript 3",
"TypeScript 3",
"Packt Books"
],
"author": "Biharck Muniz Araújo",
"license": "MIT",
"devDependencies": {
"@types/body-parser": "^1.17.0",
"@types/lodash",
"chai-http",
"@types/chai": "^4.1.7",
"@types/chai-http": "^3.0.5",
"@types/express": "^4.16.0",
"@types/mocha": "^5.2.5",
"@types/node": "^10.12.12",
"chai": "^4.2.0",
"cross-env": "^5.2.0",
"mocha": "^5.2.0",
"rimraf": "^2.6.2",
"stryker": "^0.33.1",
"stryker-api": "^0.22.0",
"stryker-html-reporter": "^0.16.9",
"stryker-mocha-framework": "^0.13.2",
"stryker-mocha-runner": "^0.15.2",
"stryker-typescript": "^0.16.1",
"ts-node": "^7.0.1",
"tslint": "^5.11.0",
"tslint-config-prettier": "^1.17.0",
"typescript": "^3.2.1"
},
"dependencies": {
"body-parser": "^1.18.3",
"express": "^4.16.4",
"js2xmlparser": "^3.0.0",
"lodash": "^4.17.11"
}
}

Now, we are good to implement the changes. Let's start with the src/util/orderApiUtility.ts file:

import { Response } from 'express'
import * as js2xmlparser from 'js2xmlparser'
import { ApplicationType } from '../models/applicationType'

export let formatOutput = (
res: Response,
data: any,
statusCode: number,
rootElement?: string
) => {
return res.format({
json: () => {
res.type(ApplicationType.JSON)
res.status(statusCode).send(data)
},
xml: () => {
res.type(ApplicationType.XML)
res.status(200).send(js2xmlparser.parse(rootElement, data))
},
default: () => {
res.status(406).send()
},
})
}

As you can see, we included the js2xmlparser package and removed the Application type as a parameter, since all operations will return either JSON or XML due to their acceptance criteria. We also added an optional parameter, called rootElement, which is responsible for describing the root XML element's name. 

The next step is to change the controllers (api, user, and order). We'll start with the api controller:

import { NextFunction, Request, Response } from 'express'
import { formatOutput } from '../utility/orderApiUtility'

export let getApi = (req: Request, res: Response, next: NextFunction) => {
return formatOutput(res, { title: 'Order API' }, 200)
}

Next, we'll move on to the user controller:

import { NextFunction, Request, Response } from 'express'
import { default as User } from '../models/user'
import { formatOutput } from '../utility/orderApiUtility'

let users: Array<User> = []

export let getUser = (req: Request, res: Response, next: NextFunction) => {
const username = req.params.username
const user = users.find(obj => obj.username === username)
const httpStatusCode = user ? 200 : 404

return formatOutput(res, user, httpStatusCode, 'user')
}

export let addUser = (req: Request, res: Response, next: NextFunction) => {
const user: User = {
// generic random value from 1 to 100 only for tests so far
id: Math.floor(Math.random() * 100) + 1,
username: req.body.username,
firstName: req.body.firstName,
lastName: req.body.lastName,
email: req.body.email,
password: req.body.password,
phone: req.body.phone,
userStatus: 1,
}
users.push(user)
return formatOutput(res, user, 201, 'user')
}

export let updateUser = (req: Request, res: Response, next: NextFunction) => {
const username = req.params.username
const userIndex = users.findIndex(item => item.username === username)

if (userIndex === -1) {
return res.status(404).send()
}

const user = users[userIndex]
user.username = req.body.username || user.username
user.firstName = req.body.firstName || user.firstName
user.lastName = req.body.lastName || user.lastName
user.email = req.body.email || user.email
user.password = req.body.password || user.password
user.phone = req.body.phone || user.phone
user.userStatus = req.body.userStatus || user.userStatus

users[userIndex] = user
return formatOutput(res, {}, 204)
}

export let removeUser = (req: Request, res: Response, next: NextFunction) => {
const username = req.params.username
const userIndex = users.findIndex(item => item.username === username)

if (userIndex === -1) {
return res.status(404).send()
}

users = users.filter(item => item.username !== username)
return formatOutput(res, {}, 204)
}

We'll then move on to the order controller:

import { NextFunction, Request, Response } from 'express'
import * as _ from 'lodash'
import { default as Order } from '../models/order'
import { OrderStatus } from '../models/orderStatus'
import { formatOutput } from '../utility/orderApiUtility'

let orders: Array<Order> = []

export let getOrder = (req: Request, res: Response, next: NextFunction) => {
const id = req.params.id
const order = orders.find(obj => obj.id === Number(id))
const httpStatusCode = order ? 200 : 404
return formatOutput(res, order, httpStatusCode, 'order')
}

export let getAllOrders = (req: Request, res: Response, next: NextFunction) => {
const limit = req.query.limit || orders.length
const offset = req.query.offset || 0

const filteredOrders = _(orders)
.drop(offset)
.take(limit)
.value()

return formatOutput(res, filteredOrders, 200, 'order')
}

export let addOrder = (req: Request, res: Response, next: NextFunction) => {
const order: Order = {
// generic random value from 1 to 100 only for tests so far
id: Math.floor(Math.random() * 100) + 1,
userId: req.body.userId,
quantity: req.body.quantity,
shipDate: req.body.shipDate,
status: OrderStatus.Placed,
complete: false,
}

orders.push(order)

return formatOutput(res, order, 201, 'order')
}

export let removeOrder = (req: Request, res: Response, next: NextFunction) => {
const id = Number(req.params.id)
const orderIndex = orders.findIndex(item => item.id === id)

if (orderIndex === -1) {
return res.status(404).send()
}

orders = orders.filter(item => item.id !== id)

return formatOutput(res, {}, 204)
}

export let getInventory = (req: Request, res: Response, next: NextFunction) => {
const status = req.query.status
let inventoryOrders = orders
if (status) {
inventoryOrders = inventoryOrders.filter(item => item.status === status)
}

const grouppedOrders = _.groupBy(inventoryOrders, 'userId')

return formatOutput(res, grouppedOrders, 200, 'inventory')
}

Now, if you run the GET user for the instance that has an acceptance of application/xml, you should see the following content, (the Accept header should be set):

<?xml version='1.0'?>
<user>
<id>85</id>
<username>Mary</username>
<firstName>Mary</firstName>
<lastName>Jane</lastName>
<email>[email protected]</email>
<password>maryjane</password>
<phone>34343434</phone>
<userStatus>1</userStatus>
</user>

The preceding code snippet shows a GET request as XML in the following screenshot:

GET user URI in XML

The same idea applies to application/json:

{
"id": 85,
"username": "Mary",
"firstName": "Mary",
"lastName": "Jane",
"email": "[email protected]",
"password": "maryjane",
"phone": "34343434",
"userStatus": 1
}

The result of the preceding code snippet can be seen in the following screenshot:

GET user URI as JSON

However, if you try any other type of acceptance, you should see an HTTP status of 406 Not Acceptable, as follows:

Request not acceptable for application/pdf format
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.137.175.113