/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./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 Dreamifier: An Application Built with Serverless Express.js
cookie-session
. We use passport.js
to log a user in and then set the cookie.// /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 // 🎉
An Express Lambda that implements Authentication with Twitter
// 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 successfully logged out</h2> <a class="button" href="/">Back Home</a> </div>` ) ) }) // Export your `app` as your Lambda. module.exports = app // 🎉
An Express Lambda that logs a user out
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 // 🎉
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 a majestic 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.
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.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.