Unable to run seneca for rest microservices - node.js

I am having the following code in my executable Js after the necessary imports.
seneca.ready(function(err){
seneca.act('role:web', {use:{
prefix: '/products',
pin: {area:'product', action:'*'},
map: {
list:{GET:true}
}
}})
var express = require('express');
var app = express();
app.use(require('body-parser').json());
app.use( seneca.export('web') );
app.listen(8082);
});
I am getting the following error while trying to run this example:
Seneca Fatal Error
Message: seneca: The export web has not been defined by a plugin.
Code: export_not_found
Details: { key: 'web' }
Thanks,
sumit

I am a beginner, I hope this snippet will be useful:
var seneca = require('seneca')()
var Web = require("seneca-web");
var Express = require('express');
var app = Express();
var config = {
Routes : [ {
prefix : '/products',
pin : {
area : 'product',
action : '*'
},
map : {
list : {
GET : true
}
}
}
],
adapter : require('seneca-web-adapter-express'),
context : app
};
seneca.use(Web, config);
seneca.add({
role: "web",
area : "product",
action : "list"
}, function(req, done) {
done(null,{result: "my list of products"});
});
seneca.ready(function(err) {
app.use(require('body-parser').json());
app.use(seneca.export('web/context'));
app.listen(8082);
seneca.act('role:web,area:product,action:list',console.log);
});
Seneca web has recently encountered some changes and you should use an adapter for express. You can see examples here on the seneca-web github page

Example : 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)
}
})

Related

how to use local swagger.json, istead of giving api controllers

I am new to swagger. I am creating an express-nodejs-typescript, rest api project. I have configured swagger and it is working fine, please see my code below.
import swaggerUi from "swagger-ui-express";
import swaggerJsdoc from 'swagger-jsdoc'
const app = express()
const swaggerOptions: swaggerJsdoc.Options = {
definition: {
openapi: "3.0.0",
info: {
title: "REST API Docs",
version: '1.0',
},
components: {
securitySchemas: {
bearerAuth: {
type: "http",
scheme: "bearer",
bearerFormat: "JWT",
},
},
},
security: [
{
bearerAuth: [],
},
],
},
apis: ['src/apis/**/*.controller.ts', 'src/schemas/*.schema.ts'],
};
const swaggerDocs = swaggerJsdoc(swaggerOptions);
app.use(
"/docs",
swaggerUi.serve,
swaggerUi.setup(swaggerDocs, { explorer: true })
);
What i want is to use local swagger.json file, instead on giving apis array apis: ['src/apis/**/*.controller.ts', 'src/schemas/*.schema.ts'],
How can I do that, please help.
something like this should work:
const app = express();
const swaggerUi = require('swagger-ui-express');
try {
const swaggerDoc = require('./your/doc/swagger.json');
app.use('/doc', swaggerUi.serve, swaggerUi.setup(swaggerDoc));
} catch (error) {
console.error('Error loading Swagger json: ', error);
}
app.listen(3000, '0.0.0.0', () => {
console.log(`🚀 Server started: http://localhost:3000/doc`);
});

Node Express - Cannot POST / error

I have been following a tutorial on how to build a full stack MEAN application and everything has been functioning fine up until this point (registration, login and authentication.)
I'm now trying to do a post request for the blog page and am receiving the following error:
<pre>Cannot POST /blogs/newBlog</pre>
All that I've done so far is create a schema, a route and then made the relevant changes to index.js. The schema file provided below is the one provided by the author of the tutorial in his respository (unlike the other two files it is in it's completed form.) The problem still persists and so I do not believe it to be the problem.
Blog schema:
/* ===================
Import Node Modules
=================== */
const mongoose = require('mongoose'); // Node Tool for MongoDB
mongoose.Promise = global.Promise; // Configure Mongoose Promises
const Schema = mongoose.Schema; // Import Schema from Mongoose
// Validate Function to check blog title length
let titleLengthChecker = (title) => {
// Check if blog title exists
if (!title) {
return false; // Return error
} else {
// Check the length of title
if (title.length < 5 || title.length > 50) {
return false; // Return error if not within proper length
} else {
return true; // Return as valid title
}
}
};
// Validate Function to check if valid title format
let alphaNumericTitleChecker = (title) => {
// Check if title exists
if (!title) {
return false; // Return error
} else {
// Regular expression to test for a valid title
const regExp = new RegExp(/^[a-zA-Z0-9 ]+$/);
return regExp.test(title); // Return regular expression test results (true or false)
}
};
// Array of Title Validators
const titleValidators = [
// First Title Validator
{
validator: titleLengthChecker,
message: 'Title must be more than 5 characters but no more than 50'
},
// Second Title Validator
{
validator: alphaNumericTitleChecker,
message: 'Title must be alphanumeric'
}
];
// Validate Function to check body length
let bodyLengthChecker = (body) => {
// Check if body exists
if (!body) {
return false; // Return error
} else {
// Check length of body
if (body.length < 5 || body.length > 500) {
return false; // Return error if does not meet length requirement
} else {
return true; // Return as valid body
}
}
};
// Array of Body validators
const bodyValidators = [
// First Body validator
{
validator: bodyLengthChecker,
message: 'Body must be more than 5 characters but no more than 500.'
}
];
// Validate Function to check comment length
let commentLengthChecker = (comment) => {
// Check if comment exists
if (!comment[0]) {
return false; // Return error
} else {
// Check comment length
if (comment[0].length < 1 || comment[0].length > 200) {
return false; // Return error if comment length requirement is not met
} else {
return true; // Return comment as valid
}
}
};
// Array of Comment validators
const commentValidators = [
// First comment validator
{
validator: commentLengthChecker,
message: 'Comments may not exceed 200 characters.'
}
];
// Blog Model Definition
const blogSchema = new Schema({
title: { type: String, required: true, validate: titleValidators },
body: { type: String, required: true, validate: bodyValidators },
createdBy: { type: String },
createdAt: { type: Date, default: Date.now() },
likes: { type: Number, default: 0 },
likedBy: { type: Array },
dislikes: { type: Number, default: 0 },
dislikedBy: { type: Array },
comments: [{
comment: { type: String, validate: commentValidators },
commentator: { type: String }
}]
});
// Export Module/Schema
module.exports = mongoose.model('Blog', blogSchema);
routes/blogs.js
const User = require('../models/user'); // Import User Model Schema
const jwt = require('jsonwebtoken');
const config = require('../config/database');
module.exports = (router) => {
router.post('/newBlog', (req, res) => { // TODO: change URL
res.send('test worked');
});
return router; // Return router object to main index.js
}
index.js
/* ===================
Import Node Modules
=================== */
const env = require('./env');
const express = require('express');
const app = express();
const router = express.Router();
const mongoose = require('mongoose');
mongoose.Promise = global.Promise;
const config = require('./config/database');
const path = require('path');
const authentication = require('./routes/authentication')(router);
const blogs = require('./routes/blogs')(router);
const bodyParser = require('body-parser');
const cors = require('cors');
const port = process.env.PORT || 8080;
// Database Connection
mongoose.connect(config.uri, {
useMongoClient: true,
}, (err) => {
// Check if database was able to connect
if (err) {
console.log('Could NOT connect to database: ', err);
message
} else {
console.log('Connected to ' + config.db);
}
});
// Middleware
app.use(cors({ origin: 'http://localhost:4200' }));
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.use(express.static(__dirname + '/public'));
app.use('/authentication', authentication);
app.use('/blogs', blogs);
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname + '/public/index.html'));
});
// Start Server: Listen on port 8080
app.listen(port, () => {
console.log('Listening on port ' + port + ' in ' + process.env.NODE_ENV + ' mode');
});
I have been enjoying this course greatly and would appreciate any help (even if it is to simply rule out possible causes.)
Error in full:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Error</title>
</head>
<body>
<pre>Cannot POST /blogs/newBlog</pre>
</body>
</html>
Your problem has to do with:
app.use('/blogs', blogs);
The blogs function should be a function taking (req, res) but it actually takes (router).
You have two options:
Create a router and pass into blogs, e.g. app.use('/blogs', blogs(router)); or
Add app.post(), e.g.
app.post('/blogs/newBlog', (req, res) => {
res.send('test worked');
});
Check spelling of your route
In my case I was posting from a url that had a missing letter.
I was doing POST /webooks/ but I needed to do POST /webhooks (note the missing h letter).
So it may also be that you misspell your routes.
Just try to change from bodyparser to express means...
instead of
app.use(bodyParser.json())
use app.use(express.json())
Please replace this:
router.post('/newBlog', (req, res) => {
res.send('test worked');
});
With this (on all your methods "get, post, use" etc):
// Make sure to always add a slash both before and after the endpoint!
router.post('/newBlog/', (req, res) => {
res.send('test worked');
});
I encounter this issue mostly if I don't add endpoint slashes properly.

Bottender - Enabling persistent_menu and get_started button on facebook

I am using NodeJS & Bottender to run my facebook messenger webhook.
So I want to have a get started button and a persistent menu. I checked & patterned my config on the examples provided on the repo like this one but I can't get it to be present on messenger yet.
here's my config.js
require('dotenv').config();
const {
FB_ACCESS_TOKEN,
FB_VERIFY_TOKEN,
APP_ID,
APP_SECRET,
} = process.env;
const { bots } = require('./global-session');
const profile = {
get_started: {
payload: '/start',
},
persistent_menu: [
{
locale: 'default',
composer_input_disabled: false,
call_to_actions: [
{
type: 'postback',
title: 'Menu',
payload: '/menu',
},
],
},
],
};
/* istanbul ignore next */
module.exports = {
messenger: {
bot: {
accessToken: FB_ACCESS_TOKEN,
appId: APP_ID,
appSecret: APP_SECRET,
mapPageToAccessToken: bots.getTokenByPageById,
profile,
},
server: {
verifyToken: FB_VERIFY_TOKEN,
path: '/messenger',
profile,
},
},
};
and here's how I use it
const { MessengerBot } = require('bottender');
const express = require('express');
const bodyParser = require('body-parser');
const { registerRoutes } = require('bottender/express');
const handler = require('./handlers');
const logger = require('./utils/logger');
const { APP_PORT, NODE_ENV } = process.env;
const server = express();
/* istanbul ignore next */
const verify = (req, res, buf) => {
req.rawBody = buf.toString();
};
server.use(bodyParser.json({ verify }));
server.use(require('morgan')('short', { stream: logger.logStream }));
const { messenger } = require('./config');
const bots = {
messenger: new MessengerBot(messenger.bot).onEvent(handler.messenger.execute),
// Define other platform bots here!
};
const initialize = async () => {
try {
registerRoutes(server, bots.messenger, messenger.server);
// Start server
server.listen(APP_PORT, () => logger.info(`ENV[${NODE_ENV}] - server is listening on port ${APP_PORT}...`));
} catch (e) {
logger.error('unable to start server!');
logger.error(e);
throw Error();
}
};
/* istanbul ignore next */
if (process.env.NODE_ENV !== 'test') {
initialize();
}
module.exports = initialize;
Background on my facebook app
- It is already published to public and approved(at least for pages_messaging)
Anything I missed to do? Any help would be appreciated!
We leave messenger profile control to developers, because we can't assume when developers want to set, update or delete bots' messenger profile.
For example, you can have a script like this:
const { MessengerClient } = require('messaging-api-messenger');
const config = require('./bottender.config');
const client = MessengerClient.connect();
const tokens = [ /* */ ]; // get tokens from somewhere
for (let i = 0; i < tokens.length; i++) {
const token = tokens[i];
client.setMessengerProfile(config.messenger.profile, {
access_token: token, // important! pass token to client api
});
}
Or you can call setMessengerProfile when you register your token to your database:
async function registerPage(page, token) {
// ....put page and token to database
await client.setMessengerProfile(config.messenger.profile, {
access_token: token, // important! pass token to client api
});
}

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

HapiJS global path prefix

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: () => {...}
}]

Resources