Using Express as RESTful Gateway for AWS Lambda - node.js

I'm currently building an application built on top of AWS lambda functions, and am wanting to use Express as a middleware and gateway for requests that will then be passed to lambda.
However, I'm wondering if there is any risk in my setup below. Basically, I am defining all the acceptable routes, using router.all('/*') to catch every request, and then if the requested route is in the routes object and requested method is correct, it will authenticate the user and then run the lambda function. I will also add logic for authentication, which data to send to lambda, etc.
My questions are:
Is it acceptable to pass the res object to the lambda execution function?
Would it be better to authenticate in a different place, or is it fine to do so within the router.all function?
Are there any concerns with scalability/handling of many requests using this simple framework?
const express = require('express');
const app = express();
var AWS = require('aws-sdk');
var router = express.Router();
const routes = {
'lambdaFunctionA': { 'method' : 'POST', 'data' : 'body'},
'lambdaFunctionB': {'method' : 'GET', 'data' : 'queryParams'},
'lambdaFunctionC': {'method' : 'GET'}
}
router.all('/*', function (req, res) {
const path = req.path.split('/')[1];
if (path in routes) {
if ( routes[path].method == req.method ) {
//authentication logic here
//if authenticated, execute lambda
executeLambda(path,someDataHere, res);
}
else
{
res.status(405).send('Method not allowed');
}
}
else
{
res.status(404).send('Not found');
}
});
app.use('/api', router);
app.listen(8080);
function executeLambda(functionName,payload, res) {
var params = {
FunctionName: functionName,
Payload: payload
};
var lambda = new AWS.Lambda();
lambda.invoke(params, function (err, data) {
if (err) {
//need to handle errors here
res.send(err.stack);
} else {
res.send(JSON.parse(data.Payload));
}
});
}

Why not use express's builtin functionality to do this instead of doing yourself? No need to invent your own routing when express comes with it built-in
function lambdaHandler(req, res){
executeLambda(path, someDataHere, res);
}
function auth(next){
//...Do auth stuff
if(!auth) return next("router");
next();
}
router.use(auth);
router.post("/lambdaFunctionA", lambdaHandler)
router.get("/lambdaFunctionB", lambdaHandler)
router.get("/lambdaFunctionC", lambdaHandler)
Lookup express middleware
This way you get Express to handle the methods etc for you instead of having to handle it yourself. It can also validate.
And on every request it uses the middleware from router.use(auth) to authenticate before it ever gets to lambdaHandler
Also it is perfectly fine to pass res to the lambda handler. It's just an object.
As a side note:
Have you seen AWS API Gateway?
It can handle routing and uses lambdas just like in your use case. Without having to manage your own server.

Related

Koa-Router : Skip the route if the request not in XHR

I have a Rest API made with Koa with some routes, but, at the same time, it will serve my Front (made with a JS framework and its own router).
The fact is, when I access from a browser "localhost/user" I want to display the front but when I reach the same url from fetch / ajax / XMLHttpRequest I want to display a JSON result (the one gave by the Koa-router).
So I would like to enable the /user route from the API only if it's called from XHR.
I did my isXMLHttpRequest middleware like this :
module.exports = async (ctx, next) => {
if(ctx.request.get('X-Requested-With') === 'XMLHttpRequest') {
return next()
}
}
Then, in my koa-router I did something like :
const Router = require('koa-router')
const isXMLHttpRequest = require("#middlewares/isXMLHttpRequest")
const router = new Router()
const user = require("#routes/user")
router.use('/user', isXMLHttpRequest, user.routes(), user.allowedMethods())
And then, it works when I do some XHR request, I have the JSON as planned, but if I try to access the /user from the browser, the API is giving me a Not Found Error and not my front...
I was looking on how to skip the router.use function if the request isn't made in XHR, but I can't find a solution...
I think it's in the middleware else condition, I have to return something, but what can I do to skip the koa-router from giving me 404 ...
Maybe you can help me ?
OK, so if you are using the SAME routes for static and XMLHttpRequests (which is probably not the best strategy), then this could work:
const Koa = require('koa')
const Router = require('koa-router')
const app = module.exports = new Koa();
isXmlRequest = (ctx) => {
// here you could also compare e.g. "accept" header
return (ctx.request.header && ctx.request.header['x-requested-with'] === 'XMLHttpRequest');
}
// static routes
const staticRouter = new Router()
staticRouter.get('/user', (ctx, next) => {
ctx.body = 'OK from static route';
next();
});
// XMLHttpRequest routes
const xmlRouter = new Router()
xmlRouter.get('/user', (ctx, next) => {
if (isXmlRequest(ctx)) {
// serve it
ctx.body = { ok: 'from JSON/XML' }
} else {
// downstream to next handler
next();
}
});
app.use(xmlRouter.routes());
app.use(staticRouter.routes());
const server = app.listen(3000)
This is not using middleware bwcause here you can only allow downstream with next but if there is no next, then this stops. There is no else ;-)
Just for reference
Not sure If I got your question right. So you have a backend that acts like a static web server AND a REST API, right?.
I would try to do it the other way round. Using e.g koa-static (https://www.npmjs.com/package/koa-static) would FIRST try to serve your files and if no matching files are found in your defines public directory, all other routes (so your REST API) are handled. Then you only have to make sure, that endpoint names do not overlap with files you are serving.

Can you call "express()" more than once in an ExpressJS application? (Or: what exactly is "express()" doing?)

I've been using Express for a while but suddenly I'm unsure about something pretty basic --
I'm trying to add custom middleware to a KeystoneJS application -- specifically I'm adding a JWT token endpoint to a TinyMCE custom field
The custom field is
export let Wysiwyg = {
type: 'Wysiwyg',
implementation: Text.implementation,
views: {
Controller: Text.views.Controller,
Field: importView('./views/Field'),
Filter: Text.views.Filter,
},
adapters: Text.adapters,
prepareMiddleware,
};
and prepareMiddleware is
function prepareMiddleware() {
const tinymcePath = dirname(require.resolve('tinymce'));
const app = express();
app.use(cors());
app.use('/tinymce-assets', express.static(tinymcePath));
app.post('/jwt', function (req, res) {
// NOTE: Before you proceed with the TOKEN, verify your users' session or access.
const payload = {
sub: '123', // Unique user id string
name: 'John Doe', // Full name of user
// Optional custom user root path
// 'https://claims.tiny.cloud/drive/root': '/johndoe',
exp: Math.floor(Date.now() / 1000) + (60 * 60) // 60 minutes expiration
};
try {
const token = jwt.sign(payload, privateKey, { algorithm: 'RS256'});
res.set('content-type', 'application/json');
res.status(200);
res.send(JSON.stringify({
token: token
}));
} catch (e) {
res.status(500);
res.send(e.message);
}
});
return app;
}
This is all called from a KeystoneJS app that has its own ExpressJS server running. What exactly is the call to express() above doing? The ExpressJS API docs say
**express()**
Creates an Express application. The express() function is a top-level function exported by the express module.
var express = require('express')
var app = express()
I always understood this to be creating a new HTTP server. Surely you don't want to do that twice in a single app unless you're serving on different ports (which I'm not trying to do)?
Similarly, the KeystoneJS docs say
If you need to provide your own custom middleware for your system you
can create a custom App and include it in your exported apps.
class CustomApp {
prepareMiddleware({ keystone, dev, distDir }) {
const middleware = express();
// ...
return middleware;
}
}
Here, again, they're calling express().
What exactly happens when you callexpress()? It starts a new Express app, according to the docs, but I always thought this was equivalent to starting a new server. Surely, I thought, you can't start two servers on the same port.
Thanks for helping clear up my confusion -- I'm obviously not seeing the forest for the trees.
express() basically just creates a stack of middleware functions. It's not a server on its own.
Because it's just a stack of middleware, an Express app can be 'mounted' into another app. An example is shown here (edited for brevity):
var sub2 = express();
sub2.get("/", (req, res) => { res.json({}); });
var app = express();
app.use("/foo", sub2);
Defining and use()ing a new Express instance is really no different from loading any other middleware stack, such as express.Router().
As for binding to ports, usually, you'll only call the listen() helper function on the upper-most Express app instance. All this does is set up a basic HTTP server (so you don't have to) and registers your Express instance as the callback. It's little different from doing http.createServer(options, myUpperMostExpressApp);.

How to add middleware to shopify app routes?

Creating a shopify app with express and mongoose. The shop's domain and access tokens are saved to the database on the callback route of the install process. The index of the app is verified with the following function:
const verifyOAuth = query => {
if (!query.hmac) {
return false;
}
const hmac = query.hmac;
delete query.hmac;
const sortedQuery = Object.keys(query).map(key => `${key}=${Array(query[key]).join(',')}`).sort().join('&');
const calculatedSignature = crypto.createHmac('sha256', config.SHOPIFY_SHARED_SECRET).update(sortedQuery).digest('hex');
if (calculatedSignature === hmac) {
return true;
}
return false;
}
How can I create a middleware function for a request to access a shop's data from the mongo database.
EX:
router.get('/content', auth, (req, res) => {
const content = Content.findOne({shopifyDomain: 'shopify-domain-here'})
res.send(content);
});
var auth = (req, res, next) => {
// Get shop domain from authentication
next();
};
Would I have to add the shop domain and hmac as a query for every get request to '/content', or should I use res.setHeader to set them as headers when the index of the app is loaded, or is there a better solution?
You cannot add routes to Shopify. You will never have a request come to you from /Content. You obviously can make that a route in your own App though, and service that route.
If you want to sent content to Shopify, you should use the App Proxy. You receive a request for content, and then you fulfill that request with content formatted as Liquid or as JSON for example.

GET request from server controller using MEAN stack

I'm using MEAN stack with MeanJs. The thing is, I have a task that requires calling a GET request from the server side (Expressjs) to another server (with a different domain name).
The code in the client side (AngularJs) calls:
$scope.getWorkflow = function() {
$http.get('/ezee', $scope.credentials).success(function(response) {
console.log(response.message);
}).error(function(response) {
console.log('error');
});
};
And the corresponding server controller function is:
exports.list = function(req, res) {
req.get('http://ezslave.io', function(q, r){
res.json({message: r.message}); // just to test
});
};
Obviously, the code below doesn't work. I'm unsure about how to make a GET request from that list function. Am I supposed to use ExpressJs or pure NodeJs for this? And how to get the correct library loaded?
Use the request module of nodejs : https://github.com/mikeal/request
for sending the http request.
var request = require("request");
exports.list = function(req, res) {
request("http://ezslave.io",function(err,response,body){
res.send(response);
});
};
Hope this helps you

How to implement HMVC structure in Node/Express

I have several "api" endpoints in my application that are used by the front-end framework for its AJAX processes. For organizational purposes, I would like to re-use the code for these endpoints to retrieve data for server-side rendering, which I do in some instances to play nicely with search engines. So, ideally I would implement some sort of HMVC setup to simply access a route on the API endpoint. Specifically I would like to get the response and perform additional actions on it before issuing the top-level response (e.g., render a view with results).
For example:
app.get('/post/recent', function(req, res) {
app.doRequest('/api/posts/', req, function(res2) {
var data = res2.body;
res.render('posts/index', data);
});
});
What's the best way to do this?
So far the best option I've come up with is:
Expose all logic in an endpoint as a method, which would be used in app.get('...', myFunction), and then I could call myFunction elsewhere outside of the express flow for that path. However, this would not give me a reliable way to run middleware specific to the endpoint (which I would also want to run on the HMVC request) unless I wrote my own middleware implementation that did not rely on express. The API endpoint has middleware that does something like if(!hasAccess) res.send(403), which I specifically do NOT want to happen in my main route since I'd want to render a nice error page instead of just sending an error code.
Example:
var getPosts = function(req) {
var deferred = q.defer()
doDatabaseQuery(req.query).then(function(response) {
deferred.resolve(response)
});
};
app.get('/api/posts', myMiddlewareFunction(), function(req, res) {
getPosts(req).then(function(response) {
res.send(response);
});
);
app.get('/post/recent', function(req, res) {
// I want to run middleware here, not in root level
getPosts(req).then(function(response) {
res.render('post/index', response);
}, function(err) {
res.render('error/noaccess');
});
});
Any better ideas? Is there a way to programmatically access an express route and get the result?
I figured this out by diving into the Express source code. There is a method called handle which I can manually invoke with modified request and response objects to get the effect I want:
app.get '/posts', (req, res) ->
req.url = '/api/posts'
newRes = _.clone(res)
newRes.send = (data, code)->
if code is 200
return res.render('posts/index', data)
else
return res.render('error')
app.handle(req, newRes)

Resources