Using Hydra

As you may remember, Hydra has a scaffolding command that helps to bootstrap our service quickly. Let's use it, and prepare our base layout. Run yo fwsp-hydra and answer the questions. You can leave most of them as default. Depending on the versions you'll use, you should get something similar to the lines shown here:

fwsp-hydra generator v0.3.1   yeoman-generator v2.0.2   yo v2.0.1
? Name of the service (`-service` will be appended automatically) imagini
? Your full name? Diogo Resende
? Your email address? [email protected]
? Your organization or username? (used to tag docker images) dresende
? Host the service runs on?
? Port the service runs on? 3000
? What does this service do? Image thumbnail and manipulation
? Does this service need auth? No
? Is this a hydra-express service? Yes
? Set up a view engine? No
? Set up logging? No
? Enable CORS on serverResponses? No
? Run npm install? No
create imagini-service/specs/test.js
create imagini-service/specs/helpers/chai.js
create imagini-service/.editorconfig
create imagini-service/.eslintrc
create imagini-service/.gitattributes
create imagini-service/.nvmrc
create imagini-service/.gitignore
create imagini-service/package.json
create imagini-service/README.md
create imagini-service/imagini-service.js
create imagini-service/config/sample-config.json
create imagini-service/config/config.json
create imagini-service/scripts/docker.js
create imagini-service/routes/imagini-v1-routes.js

Done!

'cd imagini-service' then 'npm install' and 'npm start'

Well, let's do just that. Let's enter our service folder and install dependencies. If you then start it using npm start, and open the browser and point it to our service, you should get something like this:

Not surprising, because Hydra created a different base route. To enable versioning and having different services running on the same HTTP backend, Hydra scaffolding created a route under the /v1/imagini prefix. Remember, we scaffolded Hydra with Express integration, so many of the terms we discussed earlier will be the same here:

Before we pick our previous code and integrate into Hydra, we need to add our Sharp dependency to package.json. Look up the dependencies property and add sharp. You should end up with something along these lines:

(…)
"dependencies": {
"sharp" : "^0.19.0",
"body-parser" : "^1.18.2",
"fwsp-config" : "1.1.5",
"hydra-express" : "1.5.5",
"fwsp-server-response" : "2.2.6"
},
(…)

Now, run npm install to install Sharp. Then, open the imagini-v1-routes.js file, which is under the routes folder. Basically, what it does is get a handler for Hydra and Express, prepare a generic JSON server response (that's what the fwsp-server-response module is), create an Express router, attach the / route, and then export it.

We'll keep this structure for now. I refactored the file as I'm a bit picky about indentation and quotes. I added our image route parameter and added the image upload route. I changed our previous route code to drop the /uploads route prefix, and use the new sendOk and sendError functions you see in the preceding code:

/**
* @name imagini-v1-api
* @description This module packages the Imagini API.
*/
"use strict";

const fs = require("fs");
const path = require("path");
const sharp = require("sharp");
const bodyparser = require("body-parser");
const hydraExpress = require("hydra-express");
const ServerResponse = require("fwsp-server-response");
const hydra = hydraExpress.getHydra();
const express = hydraExpress.getExpress();

let serverResponse = new ServerResponse();

express.response.sendError = function (err) {
serverResponse.sendServerError(this, { result : { error : err }});
};

express.response.sendOk = function (result) {
serverResponse.sendOk(this, { result });
};

let api = express.Router();

api.param("image", (req, res, next, image) => {
if (!image.match(/.(png|jpg)$/i)) {
return res.sendError("invalid image type/extension");
}

req.image = image;
req.localpath = path.join(__dirname, "../uploads", req.image);

return next();
});

api.post("/:image", bodyparser.raw({
limit : "10mb",
type : "image/*"
}), (req, res) => {
let fd = fs.createWriteStream(req.localpath, {
flags : "w+",
encoding : "binary"
});

fd.end(req.body);

fd.on("close", () => {
res.sendOk({ size: req.body.length });
});
});

module.exports = api;

Then, we restart our microservice, create the uploads folder under the imagini-service folder, and try to upload an image. Like before, I used curl to test it:

curl -X POST -H 'Content-Type: image/png' 
--data-binary @example.png
http://localhost:3000/v1/imagini/example.png

As expected, I received a JSON response with our size property:

{
"statusCode" : 200,
"statusMessage" : "OK",
"statusDescription" : "Request succeeded without error",
"result" : {
"size" : 55543
}
}

We can have our uploaded file in our uploads folder. We're getting there; just two more routes:

api.head("/:image", (req, res) => {
fs.access(req.localpath, fs.constants.R_OK , (err) => {
if (err) {
return res.sendError("image not found");
}

return res.sendOk();
});
});

Our check route is very similar. We just changed the return methods to use the methods  defined previously:

api.get("/:image", (req, res) => {
fs.access(req.localpath, fs.constants.R_OK , (err) => {
if (err) {
return res.sendError("image not found");
}

let image = sharp(req.localpath);
let width = +req.query.width;
let height = +req.query.height;
let blur = +req.query.blur;
let sharpen = +req.query.sharpen;
let greyscale = [ "y", "yes", "true", "1",
"on"].includes(req.query.greyscale);
let flip = [ "y", "yes", "true", "1",
"on"].includes(req.query.flip);
let flop = [ "y", "yes", "true", "1",
"on"].includes(req.query.flop);

if (width > 0 && height > 0) {
image.ignoreAspectRatio();
}

if (width > 0 || height > 0) {
image.resize(width || null, height || null);
}

if (flip) image.flip();
if (flop) image.flop();
if (blur > 0) image.blur(blur);
if (sharpen > 0) image.sharpen(sharpen);
if (greyscale) image.greyscale();

res.setHeader("Content-Type", "image/" +
path.extname(req.image).substr(1));

image.pipe(res);
});
});

Our image download method is equally similar. For this route, we're not using the JSON responses, and instead just return our image directly. This allows us to try it out on our browser:

We just migrated our service from Express to Hydra. Not much of a difference, but Hydra gives you a more robust layout, about which we'll find out more later on. Let's take a look at our third framework: Seneca.

..................Content has been hidden....................

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