Serverless Express.js with Now 2
source link: https://www.tuicool.com/articles/hit/ym22auJ
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
This post will teach you how to use Express.js effectively with serverless . Instead of shipping one monolith server, we will break down our APIs into small Express.js lambdas .
Traditionally, Express has been used as a server solution, providing routing and middleware functionality. SinceNow v2 allows us to configure routes to our serverless lambdas, we need not concern Express with routing, but only middleware. In this example, we will make use of a number of middlewares that help us meet our objective of building an application that:
- Logs a user in via Twitter
- Gets a user's Twitter handle and picture
- Applies the DeepDream net on the image
- Keeps the user logged in for repeated visits
- Allows the user to log out at a later state
Before we dive in, let us outline some benefits of Serverless Express.
When an application is managed by a monolithic instance of Express, there is a single point of failure of the entire system : if the Express process dies, the system dies . In a serverless architecture, we have distributed points of failure .
/user-zone
fails, the other routes continue faithfully serving users. This is a significant aspect of the beauty of serverless architectures. /login
route includes
passport.js
for authenticating with Twitter, while other routes do not. This separation allows each instance of Express, separated by route, to require its own modules and logic, which in turn, reduces error surface, helps debuggability, and leads to our third point,
/user-zone
route has significantly more sophisticated markup. It could prove tedious to send such markup as a JavaScript string to res.end
. Instead, we have decided, only for this route
, to use a templating engine: // /routes/user-zone/index.js app.set('view engine', 'ejs') app.set('views', join(__dirname, '../../views')) app.engine('.ejs', require('ejs').__express)
A templating engine used only in a single route
res.render('userZone')
and our template is magically
rendered, serverless style. This could lead to an impressive amount of savings, since we only include an entire templating engine
on the routes that need it, while resorting to simpler markup via string literals on simpler routes ( /routes/index.js
). The smaller size would in turn lead to a faster bootup and lower costs. Serverless provides this flexibility.
The Dreamifier : An Application Built with Serverless Express.js
cookie-session
. We use passport.js
to log a user in and then set the cookie. Since we have some common middleware in our Express app, we configure it once , and then pass the configured app around to other modules:
// /middlewares/common.js const cookieSession = require('cookie-session') module.exports = [ cookieSession({ name: 'user-from-twitter', keys: [process.env.COOK_KEY], domain: 'serverless-express.now.sh', // Cookie Options maxAge: 24 * 60 * 60 * 1000 // 24 hours }) /* Other middlewares can go here, like bodyParser(), cookieParser(), ... */ ]
Constructing our chain of common middlewares
// /util/app.js const app = require('express')() const commonMiddlewares = require('../middlewares') app.set('trust proxy', 1) app.use(...commonMiddlewares) module.exports = app
Using common middlewares throughout an exported app that is used across our routes
cookie-session
, we share this middleware across all of our routes. Other middlewares, like passport.js
are used only in the routes that require them: allowing for a proper separation of concerns, and lower costs across lambdas that do not require such functionality. TwitterStrategy
from passport-twitter
, we simply tell our application to use passport's authenticator first
, and then we process our user and persist their info into a cookie: // We need these. const passport = require('passport') const TwitterStrategy = require('passport-twitter').Strategy // Our configured app. const app = require('../../util/app') // Let us add some passport middleware. app.use(passport.initialize()) app.use(passport.session()) // Use the TwitterStrategy. passport.use( new TwitterStrategy( { consumerKey: process.env.TWITTER_TOKEN, consumerSecret: process.env.TWITTER_SECRET, callbackURL: 'https://serverless-express.now.sh/login' }, function(token, tokenSecret, profile, cb) { return cb(null, profile) } ) ) /* The next 2 functions are required to be defined by passport. Let us not worry about them for now. */ passport.serializeUser(function(user, done) { done(null, user) }) passport.deserializeUser(function(user, done) { done(null, user) }) /* This is where the magic happens: we use Express solely for MIDDLEWARE, and not for ROUTING since routing is handled by Now 2. */ // On ANY (*) GET request, we authenticate, and then: app.get('*', passport.authenticate('twitter'), (req, res) => { // When twitter calls back to this page, // 1) Clean up the session. delete req.session.passport // This adds a lot of bloat to the cookie and causes it to NOT get persisted. // 2) Get user info. const { name, screen_name, profile_image_url_https } = req.user._json // 3) Set it in req.session, which cookie-session persists in a cookie. // this will allow a user to remain logged in until either // the cookie expires, or they log out. req.session['user-from-twitter'] = { name, screen_name, profile_image_url_https } // Take the user to their area. res.redirect('/user-zone') }) // Simple, no? Then we export `app` as our Lambda. module.exports = app // :tada:
An Express Lambda that implements Authentication with Twitter
And just like that, we have an application that is able to log a user in via Twitter, and keep them logged in until they explicity logout (their decision), or the cookie expires (our decision).
How do we implement a logout lambda? Like so:
// Our configured app that has cookie-session. const app = require('../../util/app') // A basic <html> string. const baseTemplate = require('../../util/baseTemplate') // For ANY (*) GET request // Now handles ROUTING, Express handles MIDDLEWARE app.get('*', (req, res) => { // A header would be nice res.append('content-type', 'text/html') // Get some user stuff before, const { screen_name, profile_image_url_https } = req.session[ 'user-from-twitter' ] // Remove the session: cookie-session (from our imported app.js) // will handle the rest. req.session = null // Tell the user they have been logged out res.end( baseTemplate( `<div class="container"> <img class="user-image" alt="${screen_name}" src="${profile_image_url_https}" /> <h1>Success</h1> <h2>You have been sucessfully logged out</h2> <a class="button" href="/">Back Home</a> </div>` ) ) }) // Export your `app` as your Lambda. module.exports = app // :tada:
An Express Lambda that logs a user out
And now, we have a complete authentication flow from login to logout. Let us examine another concern: templates.
res.end
can become a little cumbersome. Express has powerful support for templating engines
like pug
or ejs
. Let us see how we can use them in a serverless fashion. const { join } = require('path') // Get our preconfigured app. const app = require('../../util/app') // Tell it to do more. app.set('view engine', 'ejs') app.set('views', join(__dirname, '../../views')) app.engine('.ejs', require('ejs').__express) // For ANY (*) GET, // Now does ROUTING, Express does HANDLING app.get('*', (req, res) => { // If we're not logged in, go home. if (!req.session['user-from-twitter']) { res.redirect('/') } // Add a header. res.append('content-type', 'text/html') // Get user stuff. const { screen_name, profile_image_url_https } = req.session[ 'user-from-twitter' ] // Render the /views/userZone.ejs template. res.render('userZone', { screen_name, avatar: profile_image_url_https }) }) // Export your app module.exports = app // :tada:
An Express Lambda that uses Express' template engine.
app
configured with views
and other templating values, consider exporting an appWithTemplates
once and sharing it across more complex views. The possibilities for healthy abstraction are limitless with serverless Express. now.json
at the root of our project. From there, our folder structure
becomes our routing structure for our app: we have amajestic monorepo. builds
key of now.json
signals to Now that each file that matches the pattern in src
ought to become a lambda served along the same path in the final deployed URL. { "version": 2, "builds": [ { "src": "**/*.js", "use": "@now/node" }, { "src": "*.css", "use": "@now/static" } ] }
A simplified snippet of the project's now.json to highlight routing. See the full now.json in the repositiory.
We have more content on configuring deployments inour documentation for those interested.
now
in our terminal, and we have a fully separated, fully functional application composed of lambdas
that use Express.js in a truly serverless fashion, bringing all of its benefits. In this post, we have looked at how to use Express.js in a serverless environment by breaking a monolithic Express application into smaller "expresses" that implement their own required logic. We have outlined the benefits thereof. Namely,
Additionally, we looked at code snippets that tackle real world concerns and challenges:
- Authentication.
- Middleware composition.
- Templating.
now-examples
repo for those interested. We encourage playing with the code and developing a feel for serverless Express.js. Moreover, the demo
is readily available, deployed on Now 2. We truly believe Express.js, currently a boon to JavaScript developers everywhere, blossoms into an even greater blessing when used within a serverless architecture on self-contained lambdas. We look forward to witnessing the incredible ways in which our developer communities will use these technologies together.
Until then, do not forget to share your expanded handle on Twitter!
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK