HapiJS global path prefix - node.js

I'm writing an API on HapiJS, and wondering how to get a global prefix. For example, all requests should be made to:
https://api.mysite.com/v0/...
So I'd like to configure v0 as a global prefix. The docs (here) don't seem to mention it -- is there a good way to do this in HapiJS?

If you put your API routing logic inside a Hapi plugin, say ./api.js:
exports.register = function (server, options, next) {
server.route({
method: 'GET',
path: '/hello',
handler: function (request, reply) {
reply('World');
}
});
next();
};
exports.register.attributes = {
name: 'api',
version: '0.0.0'
};
You register the plugin with a server and pass an optional route prefix, which will be prepended to all your routes inside the plugin:
var Hapi = require('hapi');
var server = new Hapi.Server()
server.connection({
port: 3000
});
server.register({
register: require('./api.js')
}, {
routes: {
prefix: '/v0'
}
},
function(err) {
if (err) {
throw err;
}
server.start(function() {
console.log('Server running on', server.info.uri)
})
});
You can verify this works by starting the server and visiting http://localhost:3000/v0/hello.

I was able to get it working for all routes with
var server = new Hapi.Server()
...
server.realm.modifiers.route.prefix = '/v0'
server.route(...)

Matt Harrisson's answer is the hapi way to do it using plugins.
Alternatively if you don't want to create a plugin just to add a prefix, you can by hand, add the prefix to all your routes.
For instance I went for something like this:
var PREFIX = '/v0';
var routes = [/* array with all your root */];
var prefixize = function (route) { route.path = PREFIX + route.path;return route; }
server.route(routes.map(prefixize));
Good point is that with something like this your can perform express-like mounting. ex:
var prefixize = function (prefix, route) { route.path = prefix + route.path;return route; }
server.route(adminRoutes.map(prefixize.bind({}, "/admin"))); //currying.
server.route(apiRoutes.map(prefixize.bind({}, "/api")));

For Hapi 19, 20 ... you can simply modify the route with map path before you register it.
// Example route
const routes = [
{
method: 'GET',
path: '/test',
handler: function (request) {
return {
status: 'success'
};
}
}
];
// transform /test -> /api/test
routes.map((r) => {
r.path = `/api${r.path}`;
return null;
});
// Register
server.route([
...routes
]);

you can always start your index.js like this
if (!global.PREFIX) {
global.PREFIX = '/v0';
}
this way everywhere inside your code you'll have access to PREFIX
that's how you can access to PREFIX
console.log(PREFIX); or var name = PREFIX+ "_somename";

Take a look at hapi-auto-route. This plugin automaticaly register your routes from a directory
// Directory structure
//
// node_modules/
// routes/
// home.js
// server.js
// package.json
// routes/home.js
'use strict';
module.exports = {
method: 'GET',
path: '/',
handler: (request, h) => 'Hello';
}
// server.js
'use strict';
const Hapi = require('hapi');
const server = Hapi.Server({
port: 3000,
host: 'localhost'
});
const init = async () => {
await server.register(require('hapi-auto-route'));
await server.start();
console.log(`Server is running at: ${server.info.uri}`);
};
process.on('unhandledRejection', (error) => {
console.log(error);
process.exit();
});
init()
and add prefix to it:

I’m late to this party but this came up in search results.. FWIW, I'm using this, built off of AdrieanKhisbe’s answer. It allows for setting multiple global prefixes and using sub-route prefixes (similar to how Django urls are laid out). Here is a sample with multiple route.js files and api route versions (the route handlers moved out for clarity):
/departments/routes.js
const { getDepartments, getDepartmentById } = require('./handlers');
module.exports = [
{ method: 'GET', path: '', handler: getDepartments },
{ method: 'GET', path: '/{id}', handler: getDepartmentById }
];
/users/routes.js
const { getUsersV1, getUserByIdV1, getUsersV2, getUserByIdV2 } = require('./handlers');
const userRoutesV1 = [
{ method: 'GET', path: '', handler: getUsersV1 },
{ method: 'GET', path: '/{id}', handler: getUserByIdV1 }
];
const userRoutesV2 = [
{ method: 'GET', path: '', handler: getUsersV2 },
{ method: 'GET', path: '/{id}', handler: getUserByIdV2 }
];
module.exports = { userRoutesV1, userRoutesV2 };
index.js
const Hapi = require('#hapi/hapi');
const departmentRoutes = require('./departments/routes');
const { userRoutesV1, userRoutesV2 } = require('./users/routes');
const init = async () => {
const server = Hapi.server({
port: 3000,
host: 'localhost',
});
const allRoutes = [];
const v1 = '/api/v1/';
const v2 = '/api/v2/';
const prefixer = (routeArray, apiPrefix, subRoutePrefix) => {
routeArray.map(route => {
route.path = `${apiPrefix}${subRoutePrefix}${route.path}`;
allRoutes.push(route);
});
};
prefixer(departmentRoutes, v1, 'departments');
prefixer(userRoutesV1, v1, 'users');
prefixer(userRoutesV2, v2, 'users');
server.route(allRoutes);
await server.start();
console.log('Server running on %s', server.info.uri);
};
process.on('unhandledRejection', err => {
console.log(err);
process.exit(1);
});
init();

Here is how I implemented mine
I created a helper function that takes an Array of Hapi.ServerRoute then map through it and concatenate the prefix then return the array.
The snippets are in Typescript so if you're using JavaScript just strip off the types
// Helper function
export function routerGroup (namespace: string, routes: Hapi.ServerRoute[]) {
return routes.map(r => {
r.path = namespace + r.path
return r
})
}
// Routes declarations
export default routerGroup('/v1/api', [
{
method: 'POST',
path: '/login',
options: {
validate: {
payload: Joi.object({
email: Joi.string().required().email(),
password: Joi.string().required().min(8).max(30)
})
},
auth: false
},
handler: Authentication.adminLogin
}
] as Hapi.ServerRoute[]
)
// Register routes to Hapi server
server.route(
[
...v1Routes,
...
]
)

server.realm.modifiers.route.prefix = '/api/v2'
await server.route(yourroutes);
This should work fine, however if you want to be able to parse all the routes automatically from your routes directory/file Hapi Router. You would be able to do something like this which will save you a lot of time.
await server.register({
plugin: HapiRouter,
options: {
routes: "./src/routes/product-routes.js",
},
}, {
routes: {
prefix: "/api/v1"
}
});
Your route file should look like this.
export default [{
method: "GET",
path: "/products",
options: {
tags: ["api", "Products"],
description: "Get All Products",
},
handler: () => {...}
}]

Related

Remove token field from bunyan log

I am using koa-bunyan-logger to log my request like this:
'use strict'
const koaBunyanLogger = require('koa-bunyan-logger');
const Koa = require('koa');
const mount = require('koa-mount')
const dbDriver = require('./database/interface')
const { userAgent } = require('koa-useragent');
const app = new Koa();
const server = app
.use(koaBunyanLogger())
.use(koaBunyanLogger.requestLogger({
updateLogFields: function (fields) {
delete fields.req.headers.authorization
}
}))
.use(mount(require('./routes/auth/jwt')))
.use(mount(require('./routes/knexplay')))
.listen(3000);
module.exports = server
What I am trying to do is to avoid logging tokens. But what it does is deleting the field from the original request object, crashing the app.
Even if I use formatRequestMessage(), it only effects msg field.
Is there a way to filter fields somehow?
Using serializer I am able to solve the problem but not sure if it is the best approach. Here is the code I used:
.use(koaBunyanLogger({
serializers: {
req: function (req) {
const { authorization, ...keepHeaders } = req.headers
return {
method: req.method,
url: req.url,
headers: keepHeaders
};
},
res: function (res) {
const { authorization, ...keepHeaders } = res.req.headers
const { req, ...keep } = res
return {
...keep,
req: {
method: res.req.method,
url: res.req.url,
headers: keepHeaders
}
};
},
}
}))

Next Js build give error 500 internal server error in some routes

I'm trying to make the page in question getServerSideProps because the content is list of products through API so getStaticProps isnt going to help with this page.
but unfortunately all the pages work perfectly on localhost but when i deploy on vercel the page in question is giving me 500 internal server error.
any help?
build log:
error:
folder structure:
export async function getStaticProps(context) {
const { host } = context.params;
const headersTn = {
domain: "tn",
};
const headersDz = {
domain: "dz",
};
const headersCi = {
domain: "ci",
};
const resTn = await fetch(`${process.env.urlApi}v1/getProductsByCountry/`, {
headers: headersTn,
}).then((res) => res.json());
const resDz = await fetch(`${process.env.urlApi}v1/getProductsByCountry/`, {
headers: headersDz,
}).then((res) => res.json());
const resCi = await fetch(`${process.env.urlApi}v1/getProductsByCountry/`, {
headers: headersCi,
}).then((res) => res.json());
const langParam = await fetch(`${process.env.urlApi}v1/getLanguage/`).then(
(res) => res.json()
);
return {
props: {
host,
tn: resTn,
dz: resDz,
ci: resCi,
dataLang: langParam,
},
};
}
export async function getStaticPaths(context) {
const paths = context.locales.map((locale) => {
return { params: { host: `${process.env.staticHost}` }, locale };
});
return {
paths,
fallback: "blocking",
};
}

Write After End Error when Reading a File using FS

I built a program where a user can send request with a PDF URL then sever download it and forward into an external API endpoint. Now, the code able to download file but it hit this error when it start to read the file.
I must admit that Promises is something I hate to learn hence I used Async Function with Awaits and in other cases I used normal functions. Promises is so hard to grasp. The syntax make it hard to read.
Code is below:
const fs = require('fs');
const url = require("url");
const path = require("path");
const http = require('http')
const rp = require('request-promise');
const app = express();
const port = 8999;
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.post('/upload-invoice', (req, res) => {
var parsed = url.parse(req.body.FileURL);
var filename = (path.basename(parsed.pathname));
var downloaded_file_path = `invoices/${filename}`;
function download() {
var options = {
hostname: parsed.hostname,
port: 2799,
path: parsed.path,
method: 'GET'
}
const file = fs.createWriteStream(`invoices/${filename}`);
const make_request = http.request(options, (response) => {
response.pipe(file);
});
make_request.end();
try {
setTimeout(function () {
upload()
}, 1000);
} catch (error) {
console.log('An Error occured when uploading file,'+error);
}
}
async function upload() {
const flow = "Upload Invoice"
var file_stream = fs.createReadStream(downloaded_file_path)
var options = {
method: 'POST',
strictSSL: true,
uri: 'https://endpoint.goes.here',
formData: {
'file': file_stream
},
headers: {
'Content-Type': 'multipart/form-data; boundary=----WebKitFormBoundaryzte28ISYHOkrmyQT'
},
json: req.body,
resolveWithFullResponse: true
}
try {
var response = await rp(options)
res.send(response.body)
}
catch (error) {
console.log(`An error on ${flow} flow for unknown user. Here is more info about error,
${error}
`)
res.send("Error")
}
}
download()
});
app.listen(port)
Update:
formData: {
name: filename,
file: {
value: fs.createReadStream(downloaded_file_path),
options: {
filename: filename,
contentType: 'application/pdf'
}
}
}
I've tried this code too but it output same error.
It works after removing json: req.body
My fault.

How to use routes in seneca-web for rest api?

I want to create rest API with seneca-web (express). I could not find any (full) documentation for a routes file used in it. I base one these examples. Let's assume i have a resource called Task. I want to have these http methods:
GET /tasks
GET /tasks/:taskId
POST /tasks
Here is routes.js:
module.exports = [
{
prefix: '/tasks',
pin: 'role:api,path:*',
map: {
all: {
GET: true,
prefix: ''
},
':taskId': {
GET: true
}
}
},
{
pin: 'role:api,path:*',
map: {
tasks: {
POST: true
}
}
}
]
and my seneca plugin for handling:
module.exports = function task (options) {
this.add({role: 'api', path: 'all'}, function (msg, respond) {
console.log(msg)
this.act('role:task,cmd:all', respond)
respond(null, [{name: 'First Task', description: 'Description of the First Task'}])
})
this.add({role: 'api', path: '*'}, function (msg, respond) {
console.log(msg)
this.act('role:task,cmd:single', {taskId: msg.args.params.taskId}, respond)
})
}
I am not sure how to separate POST and GET actions here.
I found also problematic the fact that keys in map object of routes are taken as a part of a path, eg. GET /tasks/all instead of GET /tasks.
Thanks for any help.
here is example of seneca-web with routes
=========index.js=======
const seneca = require('seneca')()
const express = require('express')()
const web = require('seneca-web')
const cors = require('cors')
var Routes = [{
prefix: '/products',
pin: 'area:product,action:*',
map: {list: {GET: true}}
}]
express.use(cors())
var config = {
routes: Routes,
adapter: require('seneca-web-adapter-express'),
context: express,
options: {parseBody: true}
}
seneca.client()
.use(web, config)
.ready(() => {
var server = seneca.export('web/context')()
server.listen('8082', () => {
console.log('server started on: 8082')
})
})
seneca.add({area: 'product', action: 'list'}, function (args, done) {
try {
done(null, {response: 'Product List'})
} catch (err) {
done(err, null)
}
})
start app using command :
node index.js
open link in your browser
http://localhost:8082/products/list

Uploading Image using Polymer and Node Js

Currently we are using Polymer for frontend. In polymer we have element “vaadin-upload”.
Issue: We are not able to fire/trace node js code. When we try to upload image then it throws POST 404 not found error.
Please check below code of Polymer and NodeJS.
Polymer:
<vaadin-upload target="http://localhost:5000/upload" method="POST" timeout="300000" headers="{'X-Custom-Header': 'value'}"></vaadin-upload>
Node Js:
var server = new Hapi.Server();
server.route({
method: 'POST',
path: '/upload',
config: {
payload:{
maxBytes:209715200,
output:'stream',
parse: false
},
handler: function (request, reply) {
var data = request.payload;
if (data.file) {
var name = data.file.hapi.filename;
var path = __dirname + "/upload/" + name;
var file = fs.createWriteStream(path);
file.on('error', function (err) {
console.error(err)
});
data.file.pipe(file);
data.file.on('end', function (err) {
var ret = {
filename: data.file.hapi.filename,
headers: data.file.hapi.headers
}
reply(JSON.stringify(ret));
})
}
}
}
});
After looking at the hapijs tutorial it looks like you need to define the port after you create the server instance.
server.connection({ port: 5000 });
Hapi will save the file to the directory for you so you don't have to handle it.
const server = new Hapi.Server();
server.connection({
port: 5000
});
server.start(function () {
console.log('server running at: ' + server.info.uri);
});
const PATH_TO_UPLOADED_FILES = '...';
server.route({
method: 'POST',
path: '/upload',
config: {
payload: {
output: 'file',
uploads: PATH_TO_UPLOADED_FILES,
parse: true,
maxBytes: 209715200
},
handler: function (request, reply) {
// file path is at: request.payload.file.path
reply('thanks');
}
}
});

Resources