API Gateway Express.js Greedy Paths to a full stack with Vue.js
What do you do when you want to take your Express.js app from your desktop to AWS? Is there an easy way to simply deploy your code “as-is” to Lambda? Yes there is!
Using the aws-serverless-express node module, along with the Abstract Factory design pattern, you can easily switch from a prototype mode to a real-life app completely self-contained and served from Lambda.
Should I be Serving HTML From Lambda?
Serving HTML, along with the rest of your APIs from a single Lambda isn’t the best path to a production app; however, if you are trying to quickly put together a demo, without a lot of organizational overhead, having some deployment tricks up your sleeve can make you look like the hero.
What are we building?
The overall demo application we are going to build is a full end to end application using Vue.js, Express.js, Lambda, and AWS DocumentDB with the MongoDB APIs. All of this will be written in Typescript and deployed with the AWS CDK.
The end result will be a simple guest book application that has two functions — adding a guestbook entry, and retrieving all guestbook entries. You can see a screenshot of the application depicted below.
Getting the Source
Before going too far, make sure to download the source from Github at the link below.
https://github.com/drewword/vue-express-lambda-mongo
Prerequisites for Running the Demo
In order to run the demo, you will need the following:
- Working node installation.
- Typescript installed.
- An AWS account.
- The AWS CDK installed.
Getting Up and Running Locally
To get up and running locally, after downloading the source, run npm install in the root directory, then change directories to the guestbook-server directory and run npm install there as well.
You can then run npm build from the same guestbook-server directory to convert the Typescript to JavaScript. After the source is converted, type the below command to run locally.
> LOCAL_MODE=1 node server.js
Local mode is the flag that triggers listening to a local port in order to test. After starting the local Express application, navigate to the below URL:
http://localhost:8080/demo-site/
Express App Walkthrough
Below is a subset of the Express.js application that will be served from Lambda. (some lines are omitted here for clarity). Here we are using the “Embedded JavaScript Template” engine to perform simple variable replacement, passing the base URL for API calls. This is a step that is not completely necessary here, although could lead as a stepping stone if you wanted to migrate static content to S3. This is essentially the endpoint to use when the Vue.js application is posting API requests.
const app = express();
const PORT = 8000;
const guestBookDAO:GuestbookDAO = new DAOFactory().getDAO();
app.set('view engine', 'ejs');app.get('/demo-site/', async(req, res) => {
let baseLambdaURL = process.env.BASE_URL || "";
res.render('index', { baseURL: baseLambdaURL });
});app.post('/demo-site/add-entry', async (req, res) => {
guestBookDAO.addGuest(req.body.firstName, req.body.lastName);
res.send("{status:'OK'");
});
The other initial item to note is the GuestbookDAO. This is a Typescript interface that utilizes the Abstract Factory Pattern to either serve a DocumentDB backed DAO, or a local mock version of a in-memory database. A quick look inside the DAOFactory reveals the switch between local and live.
export class DAOFactory {
getDAO():GuestbookDAO {
if ( process.env.LOCAL_MODE ) {
console.log ("Using Test DAO.");
return new TestDAO();
}
console.log ("Using MongoDB DAO.");
return new MongoDAO();
}
}
If the LOCAL_MODE environment variable is set, the factory will return an instance of the TestDAO, otherwise it will return the MongoDAO. When deployed to AWS, the LOCAL_MODE flag will not be defined.
The final switch is used within the main server.ts Express application to determine if the local server should be started, or if the serverless-express proxy should be used.
if ( process.env.LOCAL_MODE ) {
app.listen(PORT, () => {
console.log(`⚡️[server]: Server is running at
https://localhost:${PORT}`);
});
}
else {
const server = serverlessExpress.createServer(app);
exports.handler = (event:any = {}, context:any) => {
serverlessExpress.proxy(server, event, context);
}
Express.js doesn’t know what to do with a Lambda request, this is where the serverless express proxy comes into play. This translates AWS Lambda requests to something Express.js can understand, along with the corresponding output. The serverless express project is located here on GitHub: https://github.com/awslabs/aws-serverless-express.
Vue.js Code Walkthrough
So at this point, hopefully you have a handle of the general Express application that can either be deployed locally, or deployed to AWS Lambda using the serverless express proxy. Now it’s time to explore what application is being served. This is our Vue.js guestbook. The code for the Vue.js application can be found in guestbook-server/public/index.ejs.
This article won’t be a deep-dive into Vue.js, as that can be an entire subject to itself. I will go over the general highlights of the application however. One thing to note, and this is my personal opinion, is that Vue.js is so much easier to craft a simple application over Angular or React. I’m unsure of the scalability overall of using Vue.js in a larger application; but using Vue.js in smaller prototype applications is a perfect fit with Express.js.
Below is a subset of the Vue.js application which renders the guestbook form. Some code has been omitted for clarity.
<h1>Welcome to the Guestbook</h1>First Name:
<input v-model="firstName" class="form-control">Last Name:
<input v-model="lastName" class="form-control"><button v-on:click="submitEntry"
class="btn btn-primary">Add Name to Guestbook</button><button v-on:click="getEntries"
class="btn btn-info">Get Entries</button><ul>
<li v-for="user in users">
{{user.firstName}} {{user.lastName}}
</li>
</ul>
The two inputs for first name and last name, are backed by the Vue.js application data. The two other form controls are a button that triggers the submit entry functionality, and a button to get all entries from MongoDB (DocumentDB).
Below is a main highlight from the Vue.js application definition in the same file, defining the method for submitting entries. I have used the axios library here to handle posting to the server.
var app = new Vue({
el: '#app',
data: {
firstName: '',
lastName: '',
users:[]
},
methods: {
submitEntry: function() {
axios.post ("<%= baseURL %>/demo-site/add-entry", {
firstName: this.firstName,
lastName: this.lastName
}).then((response) => {
console.log("Added guestbook entry.");
}, (error) => {
console.log(error);
});
Building the Infrastructure with the AWS CDK
The AWS CDK code establishes two separate stacks. The initial stack creates the Lambda, API Gateway entry, and the DocumentDB cluster. The second stack modifies the Lambda parameters with the DocumentDB connection string. Here I ran into a few cases where the CDK did not want to deploy due to circular references. I found migrating the addition of the MongoDB connection string to the second stack cleared everything up. The primary code for the CDK deployment can be found in the file full-vue-proto-stack.ts.
API Greedy Path Variables
When defining the API Gateway endpoint, using the method addProxy will proxy all calls to the paths of the Lambda in order to route to the Express.js application.
var apiEndpoint = new apigateway.RestApi(this, "demohtml-lambda", {
restApiName: "RESTExpress",
description: "Simple hello world lambda."
});
apiEndpoint.root.addProxy({
defaultIntegration:
new apigateway.LambdaIntegration(lambdaFunction)
});
This puts an entry in API Gateway that looks like below:
/demo-site/{proxy+}
For details on using this within API Gateway, see the AWS release notes here: https://aws.amazon.com/blogs/aws/api-gateway-update-new-features-simplify-api-development/. Using greedy paths in addition to using the serverless express library is the secret sauce to a fast and easy deployment.
Building and Deploying to AWS
From the root directory of the project, run the following.
> npm run build
> cdk deploy --all
After deploying with the CDK, the CDK will output display an item similar to below.
FullVueProtoStack.demohtmllambdaEndpointXXXX=[SOME URL]
Use that URL with the addition of /demo-site/ to display the results from Lambda.
Final Bits with MongoDB
This article would be entirely too long to go in-depth on utilizing MongoDB within a Node environment; however, hopefully the simple usage can get you started. MongoDB compatibility for AWS DocumentDB can be a great way to use an AWS managed service for your MongoDB needs. Note that if you are using advanced features of MongoDB, please check for API compatibility.
Locate the file MongoDAO.ts within the guestbook-server directory for how to connect, insert items into the database, and retrieve a collection of entries.
Final Thoughts
This article is probably longer than I would have liked, and going in-depth on any one topic would make the article length unbearable. However, my goal was to make each part of the code as succinct as possible to illustrate the full end-to-end process.
Don’t forget to check out the code on GitHub here: https://github.com/drewword/vue-express-lambda-mongo.
In addition, don’t forget to run cdk destroy as well to avoid getting an unexpected AWS bill. The DocumentDB policy is set to destroy, so anything created during the demo will be eliminated in favor of keeping a small bill. In production, you would want to change this policy to keep the database around.
Happy AWS coding!