Skip to main content

Deploying a Node/Express App to Cyclic.SH

Objectives

  • Deploy to Cyclic.sh
  • Learn basics of lambda on aws
  • Learn about memory store for sessions
  • Learn about CI/CD

Intro

Project 1 was a static application that required no code to run on a server. However, SEI Project 3 and beyond are full-stack web applications that need to run code on a backend server.

Cyclic is a good alternative to Heroku if you are looking for a modern cloud architecture, serverless hosting infrastructure, easy on-boarding experience

Important Messages from the docs

CI/CD A pipeline is built into every app and integrated with GitHub. Changes are live in a few seconds with every git push. Each deployment is registered, along with build/deploy logs pointing to the specific git change set. You will know exactly what changes went out and when


Cloud Architecture

Cyclic apps are built and deployed into AWS. We pre-provision a serverless app using cloudformation.

At first launch we select an available stack and deploy the code for your app into the existing lambda.


AWS VOCABULARY

cloudformation - AWS CloudFormation is an AWS service that uses template files to automate the setup of AWS resources like ec2 instances, s3 buckets, lambda functions. This enables efficient and quick distribution of services. Cyclic uses this under the hood for us.

lambda - AWS Lambda is a serverless compute service that runs your code in response to events and automatically manages the underlying compute resources for you. These events may include HTTP Requests (what our express server does), S3 bucket changes, DynamoDB, and others!

info

So this means, every time we push to github Cyclic, we will update our lambda to use our latest code of our recent git push! Very cool!


Deployment

  1. follow the directions to setup your code on the lambda
  • Sign up: here
  • Using github as your login
  • Choose "Link my own", and type in your repo name
  • Click deploy
  • Approve "Cyclic - Preview" app in github
  • Watch the terminal for your deployment logs

  1. Set up your environment variables!
  • on cyclic navigate to the Variables tab.
  • You need to encode all the environment variables your app needs to run

  • Remember you're putting your code on a different machine (lambda), and you're setting the Variables that only exist while our server code on the lambda is running.

PORT Number

tip

Cyclic supports but does not require setting a port via the environment variables.

Hosting services determine what port a Node application will listen for HTTP requests on.

Hosting services set a Node environment variable, process.env.PORT, that our application needs to listen on during production (not development).

If your Express application was generated using express-generator, you're all set thanks to this line of code in the www file:

var port = normalizePort(process.env.PORT || '3000');

However, if you created your Express app "from scratch", then you need to ensure that the app listens on process.env.PORT if it's deployed.

Node Version

Specifying a Node version in the package.json is not necessary.

Specifying a Start Script

Again, you're already set if you generated your app using express-generator.

Here's some useful information on start scripts here. Highly recommend reading through it.

Database Connection

Connections in a Serverless Runtime (lambda environment) MongoDB is not an on-demand database and its connection mechanism is persistent, it can also take a moment to establish the connection for the first time. For best performance, avoid making a connection inside a route handler.* - per the docs

So this means we need to establish our database connection as soon as our lambda wakes up when it receives a new http request. essentially it means we need to establish our database connection and then start our server, on every request.

We need to update our config/database.js file, it will be a bit different then the docs since we're using express-generator.

config/database
const mongoose = require("mongoose");

// export the function that creates a database connection
module.exports = {
connectDB,
};

async function connectDB() {
try {
const conn = await mongoose.connect(process.env.DATABASE_URL);
console.log(`MongoDB Connected: ${conn.connection.host}`);

} catch (err) {
console.log(err, ' connecting to mongodb')
process.exit(1);
}
}

Then we need to setup the database connection before our server starts, we can use that function in our

bin/www
// top of the file import the connectDB function
const { connectDB } = require("../config/database");


// Rest of code...

/**
* Create HTTP server.
*/
var server = http.createServer(app);



/**
* Listen on provided port, on all network interfaces.
*/
// Call our connectDB function then start our server listening on the port!
connectDB().then(() => {
server.listen(port);
server.on("error", onError);
server.on("listening", onListening);
});

  • we just connect to mongodb by calling our function than we have our express server starting listening on the port.
  • This will guarantee our database is always connected when the lambda function wakes up when it receives an http request.

Setting up a session store

In development, our session data, is stored in the servers memory. Unfortunately that means every time our app starts or stops running (staring and shutting down the server) we are no longer capable of keeping track of who was logged in.

This will happen a lot with a lambda in a serverless environment.

app.use(session({
secret: process.env.SECRET,
resave: false,
saveUninitialized: true
}))

The session cookie used with passport lets us keep track of who the logged in user is when an http request is made to our server

So to tackle this issue we will install connect-mongo.

npm install connect-mongo

This will allow us to use our mongodb database on atlas to track our session data, so we can maintain the logged in users regardless of whether our app starts or stops, because we can load the data from the database.

setup

  • server.js
server.js
//import near the top of the file
const MongoStore = require('connect-mongo');

// rest of code then update the middleware
// setting up our session to use the Mongostore

app.use(session({
store: MongoStore.create({
mongoUrl: process.env.DATABASE_URL
}),
secret: process.env.SECRET,
resave: false,
saveUninitialized: true
}));

Updating deployed app

So we just changed some code in our app to deploy that we just need to push our main branch on github.

deploy

git add -A
git commit -m "configured deployment code!"
git push origin main

Update the App's Google OAuth Registration

As discussed in the OAuth lesson, the time would come after deployment that we would need to update the app's Authorized redirect URIs in the Google Developer Console for the project.

Ensure that the correct project is selected in the dropdown:

Click the project's Credentials menu choice on the left, then click the name listed in the OAuth 2.o Client IDs:

Now click the + ADD URI button that's below Authorized redirect URIs:

Finally, enter the exact URI that your app is has.

  • on cyclic click on the overview tab and then copy the App URL link

Assign that to your GOOGLE_CALLBACK environment variable and click the SAVE button:

Browse to the App

Okay, now the big moment, browse to your deployed app.

Remember you can find your deployed link under your app on cylic in the Overview tab

Congrats on deploying to Cyclic

Monitoring deployment

Click on Deployments


Monitoring HTTP requests and the server's terminal

Click on the Logs tab

  • Here on the left, you can see the http requests coming into your express server running on your lambda.

  • On the write you can see all the console.logs that are occurring in your application that is running on your lambda on aws.

Troubleshooting

Deployment messages, error messages, as well as the output you typically see in your terminal when running your app during development (including output from your code's console.log statements) can be viewed by going to the *Logs tab on cyclic


Since the proper setting of environment variables is a common source of problems, always double check that you have all the variables set that your app needs in order to run.

  • on cyclic navigate to the Variables tab.
  • check your variables are correct, spelling and all!

Docs