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

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

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`);
});

Setting Bearer Token in the header section while testing with Supertest

I'm trying to add the Bearer in the header section in POST request while testing with Supertest. I tried many methods. I'm a beginner in testing. Please suggest some better ways to achieve this.
here is my sample code. What is wrong with this?
it('POST/userAuth', async (done) => {
const res = await request
.post('/v1/user/haha')
.set('Authorization', `bearer ${Token}`)
.send({
title: 'Some random text',
options: [
{ start: hello, end: world },
{ start: good, end: bye },
],
});
You can set a request header like this:
const request = require('supertest');
const express = require('express');
const app = express();
const TOKEN = 'some_token';
describe('POST /some-url', function() {
it('does something', function(done) {
request(app)
.post('/some-url')
.send({ body: 'some-body' })
.set('Authorization', `Bearer ${TOKEN}`)
.expect(200, done);
});
});
in superagent docs you can find specialized .auth method
interface Request extends Promise<Response> {
auth(user: string, pass: string, options?: { type: "basic" | "auto" }): this;
auth(token: string, options: { type: "bearer" }): this;
...
}
(supertest is using superagent under the hood)
I prefer to set auth in before function for all tests in the set.
import * as Koa from 'koa';
import * as http from 'http';
import { agent as superagent } from 'supertest';
import { UserController } from './user-controller';
const testBearerToken = 'test-bearer-token';
describe('user controller', async () => {
context('simple user', async () => {
it('should save user', async () => {
const response = await test
.post('/v1/user/haha')
// you can here, but rather set it in the before function
//.auth(testBearerToken, { type: 'bearer' });
.send({
title: 'Some random text',
options: [
{ start: 'hello', end: 'world' },
{ start: 'good', end: 'bye' },
],
});
// .expect(...)
// expect(response)...
});
});
let test;
before(async () => {
const app = new Koa();
const userController = new UserController();
app.use(userController.middleware());
test = superagent(http.createServer(app.callback()))
.auth(testBearerToken, { type: 'bearer' });
});
});

Uploading Files on production server returns either CORS error or POST 400 Bad Request using Apollo-Graphql

I'm having trouble on uploading files to my production server, in a local environment, everything works fine just as expected, but when I try to do a a Mutation containing file uploads (and only on those mutations) it throws me a CORS error. I'm using Spaces to upload the files and save them into my Database.
app.ts (Back-end):
const configureExpress = async () => {
const app: express.Application = express();
app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use('/services', servicesRoute);
const { typeDefs, resolvers } = await buildSchema;
const schema = makeExecutableSchema({ typeDefs, resolvers });
const server = new ApolloServer({
schema,
playground: true,
introspection: true,
uploads: {
maxFileSize: 1000000000,
maxFiles: 100,
},
context: async ({ req }) => ({
auth: await Auth.getUser(req),
}),
formatError: (err) => ({
message: err.message,
code: err.extensions && err.extensions.code,
locations: err.locations,
path: err.path,
extensions: err.extensions && err.extensions.exception,
}),
});
server.applyMiddleware({ app });
return app;
};
export default () => database.connect().then(configureExpress);
client-auth.ts (On the Front-end):
const errorLink = onError(({ graphQLErrors }: any) => {
if (graphQLErrors) {
console.log(graphQLErrors);
graphQLErrors.map(({ message }: any) => console.log(message));
graphQLErrors.map(({ code }: any) => {
if (code === 'UNAUTHENTICATED') {
persistStore(store)
.purge()
.then(() => {
window.location.reload();
});
}
return true;
});
}
});
const authLink = setContext((_, { headers }) => {
const token = store.getState().auth.user!.token;
return {
headers: {
...headers,
authorization: `Bearer ${token}`,
},
};
});
const uploadLink = createUploadLink({
uri: 'https://api.example.com.br/graphql'
// uri: 'http://localhost:4000/graphql',
});
const client = new ApolloClient({
link: ApolloLink.from([errorLink, authLink, uploadLink]),
cache: new InMemoryCache(),
defaultOptions: {
query: {
fetchPolicy: 'network-only',
},
},
});
resolver.ts
return propertyModel.create({
...data
} as DocumentType<any>).then(async property => {
const user = await userModel.findById(input.userId);
if (!user) throw new UserNotFound();
await ownerModel.findByIdAndUpdate(user.owner, {
$push: {
properties: property.id,
}
});
if (input.images) {
input.images.forEach(async image => {
const uploadedImage = await FileS3.upload(image, {
path: 'images',
id: propertyId.toHexString(),
});
await property.updateOne({$push: {images: uploadedImage}});
});
}
if (input.scripture) {
const uploadedScripture = await FileS3.upload(input.scripture, {
path: 'documents',
id: propertyId.toHexString(),
});
await property.updateOne({scripture: uploadedScripture});
}
if (input.registration) {
const uploadedRegistration = await FileS3.upload(input.registration, {
path: 'documents',
id: propertyId.toHexString(),
})
await property.updateOne({
registration: uploadedRegistration,
});
};
if (input.car) {
const uploadedCar = await FileS3.upload(input.car, {
path: 'documents',
id: propertyId.toHexString(),
});
await property.updateOne({
car: uploadedCar,
});
};
if (input.ccir) {
const uploadedCcir = await FileS3.upload(input.ccir, {
path: 'documents',
id: propertyId.toHexString(),
});
await property.updateOne({
ccir: uploadedCcir,
});
};
if (input.itr) {
const uploadedItr = await FileS3.upload(input.itr, {
path: 'documents',
id: propertyId.toHexString(),
});
await property.updateOne({
itr: uploadedItr,
});
};
if (input.subscription) {
const uploadedSubscription = await FileS3.upload(input.subscription, {
path: 'documents',
id: propertyId.toHexString(),
});
await property.updateOne({
subscription: uploadedSubscription
});
return property;
});
};
I'm really lost regarding this error, is this an actual server error? (Production server is on DigitalOcean in Ubuntu) or something wrong regarding the code?
For CORS, if you are using the latest version of ApolloServer then turn on the CORS:
const server = new ApolloServer({
cors: {
credentials: true,
origin: true
},,
...
});
//also apply it here
server.applyMiddleware({ app,
cors: {
credentials: true,
origin: true
}
});
400 status code is returned for bad request which happens when a client sends a malformed request to server, You need to verify that your client is sending the correct data and headers on via correct HTTP verb (post/get etc)
If any one happens to have this same problem, here's how I solved.
After digging through the code I realized that in the server I was receiving a Maximum call stack size exceeded, upon looking further to this problem I realized that It was an error regarding the Graphql-Upload dependency, I fixed it by removing it as a dependency and added the following on my package.json:
"resolutions": {
"fs-capacitor":"^6.2.0",
"graphql-upload": "^11.0.0"
}
after this I just executed this script: npx npm-force-resolutions. And It worked all fine.

Unable to run seneca for rest microservices

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

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