Create config variables in sails.js? - node.js

I'm converting an app of mine from Express to sails.js - is there a way I can do something like this in Sails?
From my app.js file in Express:
var globals = {
name: 'projectName',
author: 'authorName'
};
app.get('/', function (req, res) {
globals.page_title = 'Home';
res.render('index', globals);
});
This let me access those variables on every view without having to hardcode them into the template. Not sure how/where to do it in Sails though.

You can create your own config file in config/ folder. For example config/myconf.js with your config variables:
module.exports.myconf = {
name: 'projectName',
author: 'authorName',
anyobject: {
bar: "foo"
}
};
and then access these variables from any view via global sails variable.
In a view:
<!-- views/foo/bar.ejs -->
<%= sails.config.myconf.name %>
<%= sails.config.myconf.author %>
In a service
// api/services/FooService.js
module.exports = {
/**
* Some function that does stuff.
*
* #param {[type]} options [description]
* #param {Function} cb [description]
*/
lookupDumbledore: function(options, cb) {
// `sails` object is available here:
var conf = sails.config;
cb(null, conf.whatever);
}
};
// `sails` is not available out here
// (it doesn't exist yet)
console.log(sails); // ==> undefined
In a model:
// api/models/Foo.js
module.exports = {
attributes: {
// ...
},
someModelMethod: function (options, cb) {
// `sails` object is available here:
var conf = sails.config;
cb(null, conf.whatever);
}
};
// `sails is not available out here
// (doesn't exist yet)
In a controller:
Note: This works the same way in policies.
// api/controllers/FooController.js
module.exports = {
index: function (req, res) {
// `sails` is available in here
return res.json({
name: sails.config.myconf.name
});
}
};
// `sails is not available out here
// (doesn't exist yet)

I just made a service which delivers a value:
maxLimbs: function(){
var maxLimbs = 15;
return maxLimbs;
}

Related

How can I export async await functions to use like a utils file?

I have a utils file where I have async await functions.
I am exporting these functions like this:
module.exports.pressing = pressing;
module.exports.login = login;
module.exports.logout = logout;
module.exports.getImage = getImage;
module.exports.decodeUnicodeCharacters = decodeUnicodeCharacters;
module.exports.initApp = initApp;
What I would like to achieve is that through the protractor configuration file I can call the functions of the utils file like this:
browser.params.utils.pressing(protractor.Key.DOWN);
What is the best way to do it?
Create a module and require anywhere you want to use it
utils.js
module.exports = {
pressing: function () {
// what it does
},
login: function () {
// what it does
},
logout: function () {
// what it does
},
getImage: function () {
// what it does
},
decodeUnicodeCharacters: function () {
// what it does
},
initApp: function () {
// what it does
},
};
then anywhere in project
let utils = require('PATH/TO/utils.js')
await utils.login();
Make it global
config
// ...
onPrepare() {
global.utils = {
login: function () {
// what it does
},
}
}
// ...
anywhere in project
await utils.login() // no need to require

Localized routes in koa

I'm developing a site with multiple languages. Some routes will therefore also have to be localized and I'm not sure how to do this properly.
I'm using #koa/router for routing.
For this example it's only English and Swedish but the site will handle more languages.
I can setup routes to match words in different languages like
router.get('/(create-account|skapa-konto)/', (ctx, next) => {
ctx.body = translate('signup_welcome');
await next();
});
But, I want the English site to only respond to '/sign-up' and send 404 for '/skapa-konto' (and vice versa).
In the real world the route would point to some controller function. So if I set up individual routes for each language I would have to change all localized routes manually should the controller function change in the future. That's something I would like to avoid ;)
Any suggestions?
I ended up solving this by extending the Router like this:
const LocalizedRouter = class extends Router {
/**
* Set up route mapping
* #param {object} options
*/
constructor(options) {
if (!Array.isArray(options.languages)) {
throw new TypeError('Languages must be of type Array');
}
super(options);
this.languages = options.languages;
}
/**
* Router function for GET method
* #param {string | Object<string, string>} RouteCollection
*/
get(routes, func) {
if (typeof(routes) === 'string') {
super.get(routes, func);
return;
}
if (typeof(routes) === 'object') {
for(const key in routes) {
if(!this.languages.includes(key)) {
continue;
}
if(typeof(func) !== 'function') {
throw new TypeError('Middleware must be a function');
}
const checkLanguageAndMount = async (ctx, next) => {
if(ctx.state.lang !== key) {
return next();
}
return func(ctx, next);
};
super.get(routes[key], checkLanguageAndMount);
}
return;
}
throw new TypeError('"Routes" must be a string or an object');
}
};
I can then set up my routes like this:
const myRouter = new LocalizedRouter({
languages: ['en', 'sv']
});
myRouter.get({
'en': '/create-account',
'sv': '/skapa-konto'
}, (ctx, next) => {
ctx.body = translate('signup_welcome');
await next();
};
This can probably be cleaned up but it does solve what I wanted to do.
EDIT: Fixed bug that caused 404 if two languages had identical paths
This problem interested me so I created a small github repo with some code. I'll try to explain here:
I created an array with some options:
const localeConfig = [
{
locale: "en",
routes: [
{
path: "/sign-up",
controllers: [enController],
method: "GET",
},
],
prefix: false,
},
{
locale: "se",
routes: [
{
path: "/skapa-konto",
controllers: [seController],
method: "GET",
},
],
prefix: false,
},
];
I then pass this object to a setupRoutes function that basically iterates the array, generating all the routes according to those options.
const setupRoutes = (localeConfig) => {
// Have some check to prevent duplicate routes
localeConfig.forEach((opt) => {
// Adding prefix according to option
const localePrefix = opt.prefix ? `/${opt.locale}` : "";
opt.routes.forEach((route) => {
const path = `${localePrefix}${route.path}`;
router[route.method.toLowerCase()].apply(router, [
path,
...route.controllers,
]);
});
});
};
So, for instance, if you were to change any of the controllers in either language you would only need to update the specific locale object.route.controllers. I imagine you could even have each different locale in a different file to have some modularity.
The github repo is here and I would really like to have you contribute to it if you have any idea on how to improve this.
Cheers!

Extracting creation of a mongoose model out of server file

I'm currently building a generic express API and I'm finding it difficult to extract code from my server.js file
I have the following in my server.js file
app.post('/parser', (req, res) => {
var todo = new Todo({
text: req.body.text,
});
todo.save().then((doc) => {
res.send(doc);
}, (e) => {
res.status(400).send(e);
})
});
Where my todo is just a mongoose model in a separate file
var Todo = mongoose.model('Hello123', {
text: {
type: String,
}
});
module.exports = { Todo };
I want to pass any string from my server file as a variable for my database name, so in this case pass any variable where I have 'Hello123'
Is it possible to do this? I've been trying to export the creation of the model as function and call this from the server file however this hasn't worked
Not the end of the world if I can't however I like keeping all functionality etc. out of my server.js file such that it is easier to read
Change your database file to export a function which accepts the model name like this:
module.exports = function(name) {
var Todo = mongoose.model(name, {
text: {
type: String,
}
});
return Todo;
}
Then your server.js import statement can look like:
var modelName = 'Hello123';
var Todo = require('./database.js')(modelName);
This should work.

App Engine Node.js: how to link app logs and requests logs

I am using Node.js on App Engine Standard and Flexible.
In the logs viewer, is it possible to display application logs nested inside request logs?
Yes it is possible to correlate application logs and request logs. This is the end result in the Logs Viewer:
To achieve this you can either:
Use both the #google-cloud/trace-agent and #google-cloud/logging-bunyan modules in your application. When you do so, your logs are automatically annotated with the correct Trace ID (see docs for Bunyan).
Extract the trace ID from the request header
If you do not want to use the Trace module, you can extract the trace ID from the request header, use the following code to extract the traceId:
const traceHeader = req && req.headers ? req.headers['x-cloud-trace-context'] || '' : '';
const traceId = traceHeader ? traceHeader.split('/')[0] : '';
Then, you need to populate the trace attribute of your log entries. When using the Bunyan logger, use the following code:
logger.info({
'logging.googleapis.com/trace': `projects/${project}/traces/${traceId}`
}, 'your message');
I also faced the same issue sometime back and did some workaround to make it. But in the above-mentioned solution might not help in some use cases where you have don't req, res object reference.
So here the solution. it will group all the logs under the request log.
Also created -> NPM Module
File Name: correlate-logs.js
import bunyan from 'bunyan';
import { LOGGING_TRACE_KEY } from '#google-cloud/logging-bunyan';
import cls from 'cls-hooked';
import uuid from 'uuid/v1';
/**
* CreateLogger will return loggerContextMiddleware and log.
* Bind the loggerContextMiddleware on top to corelate other middleware logs. `app.use(loggerContextMiddleware);`
* then you can log like this anywhere `log.info('This is helpful to see corelated logs in nodejs.)` and it will show with-in reqeust log.
* #param {*} options
*/
export default function createLogger(projectId, bunyanLoggerOptions) {
if (!projectId || !bunyanLoggerOptions) throw new Error('Please pass the required fields projectId and bunyanLoggerOption');
const ns = cls.createNamespace(`logger/${uuid()}`); // To create unique namespace.
const logger = bunyan.createLogger(bunyanLoggerOptions);
/**
* Express Middleware to add request context to logger for corelating the logs in GCP.
* #param {*} req
* #param {*} res
* #param {*} next
*/
const loggerContextMiddleware = (req, res, next) => {
const traceHeader = (req && req.headers && req.headers['x-cloud-trace-context']) || '';
if (traceHeader) {
ns.bindEmitter(req);
ns.bindEmitter(res);
const traceId = traceHeader ? traceHeader.split('/')[0] : '';
const trace = `projects/${projectId}/traces/${traceId}`;
ns.run(() => {
ns.set('trace', trace);
next();
});
} else {
next();
}
};
/**
* Helper method to get the trace id from CLS hook.
*/
function getTrace() {
if (ns && ns.active) return ns.get('trace');
return '';
}
/**
* Simple wrapper to avoid pushing dev logs to cloud.
* #param {*} level
* #param {*} msg
*/
function printLog(level, ...msg) {
const trace = getTrace();
if (trace) { logger[level]({ [LOGGING_TRACE_KEY]: trace }, ...msg); } else { logger[level](...msg); }
}
/**
* Little wrapper to abstract the log level.
*/
const log = ['trace', 'debug', 'info', 'warn', 'error', 'fatal'].reduce((prev, curr) => ({ [curr]: (...msg) => printLog(curr, ...msg), ...prev }), {});
return { loggerContextMiddleware, log };
}
File Name: logger.js
import { LoggingBunyan } from '#google-cloud/logging-bunyan';
import createLogger from '../lib/corelate-logs';
import { getProjectId, ifDev } from './config';
// Creates a Bunyan Stackdriver Logging client
const loggingBunyan = new LoggingBunyan();
let loggerOption;
if (ifDev()) {
const bunyanDebugStream = require('bunyan-debug-stream'); // eslint-disable-line
loggerOption = {
name: 'my-service',
streams: [{
level: 'info',
type: 'raw',
stream: bunyanDebugStream({
forceColor: true,
}),
}],
serializers: bunyanDebugStream.serializers,
};
} else {
loggerOption = {
name: 'my-service',
level: 'info',
streams: [loggingBunyan.stream('info')],
};
}
const { loggerContextMiddleware, log } = createLogger(getProjectId() || 'dev', loggerOption);
export { loggerContextMiddleware, log };
Hope this helps somebody.

node assetmanager with more modules

I'm trying to set up assetmanager
for my blog that has three modules
default
login
admin
I tried like
assets.json
{
"css": {
"app":{
"public/src/dist/default/css/dist.min.css": [
"public/src/assets/default/css/*.css"
]
},
"login":{
"public/src/dist/login/css/dist.min.css": [
"public/src/assets/default/css/*.css"
]
},
"admin":{
"public/src/dist/admin/css/dist.min.css": [
"public/src/assets/admin/css/*.css"
]
}
}
}
express.js
assetmanager.init({
js: assets.js,
css: assets.css,
debug: (process.env.NODE_ENV !== 'production'),
webroot: 'public'
});
// Add assets to local variables
app.use(function(req, res, next) {
res.locals({
assets: assetmanager.assets
});
next();
});
console.log(assetmanager.assets);
but console.log(assetmanager.assets);
give me a empty array []
so is there a way to manage assetmanager
with more than one module ?
the best way I found up to now
is like in my controllers:
'use strict';
var assetmanager = require('assetmanager');
exports.render = function(config) {
var assets = require(config.sroot+'/config/assets.json');
assetmanager.init({
js: assets.js.app,
css: assets.css.app,
debug: (process.env.NODE_ENV !== 'production'),
webroot: 'public'
});
return function(req, res) {
res.render('layouts/default', {appTitle:'ilwebdifabio',assets:assetmanager.assets});
}
};
but it's quite ugly and I have
duplicate code :(
END UP
There is no way to use assetmanager module
in different modules (login,default,admin).
Modules are automatically cached by the Node.js application upon first load. As such, repeated calls to require() - the global method that loads modules - will all result in a reference to the same cached object.
so you end up ie if you use in a module
to the have the dedicate assets in all other module so
I worked it out with :
'use strict';
var _ = require('lodash');
module.exports = function (path,route) {
var env = (process.env.NODE_ENV === 'production') ? 'production' : null;
var debug = (env !== 'production');
var data = require(path+'/config/assets.json');
var assets = {
css: [],
js: []
};
var getAssets = function (pattern) {
var files = [];
if (_.isArray(pattern)) {
_.each(pattern, function (path) {
files = files.concat(getAssets(path));
});
} else if (_.isString(pattern)) {
var regex = new RegExp('^(//)');
if (regex.test(pattern)) {
// Source is external
//For the / in the template against 404
files.push(pattern.substring(1));
} else {
files.push(pattern);
}
}
return files;
};
var getFiles = function () {
var current = data[route];
_.each(['css', 'js'], function (fileType) {
_.each(current[fileType], function (value, key) {
if (!debug) {
assets[fileType].push(key);
} else {
assets[fileType] = assets[fileType].concat(getAssets(value));
}
});
});
};
var getCurrentAssets = function(){
return assets;
};
getFiles();
return {
getCurrentAssets: getCurrentAssets
};
};
in the controller
var assetmanager = require(config.sroot+'/utils/assetsmanager')(config.sroot,'app');
res.render('layouts/default', {
assets:assetmanager.getCurrentAssets()
});
There is a new version of assetmanager 1.0.0 that I believe accomplishes what you're trying to do more effectively. In the new version you can break apart your assets into groups so that you can support multiple layouts. The github has a complete example here but essentially your asset files ends up looking something like this:
{
"main": {
"css": {
"public/build/css/main.min.css": [
"public/lib/bootstrap/dist/css/bootstrap.css",
"public/css/**/*.css"
]
},
"js": {
"public/build/js/main.min.js": [
"public/lib/angular/angular.js",
"public/js/**/*.js"
]
}
},
"secondary": {
"css": {
"public/build/css/secondary.min.css": [
"public/css/**/*.css"
]
},
"js": {
"public/build/js/secondary.min.js": [
"public/js/**/*.js"
]
}
}
}
And then in your layouts you just include the group you want. Hopefully that helps out.

Resources