Learn how web apps work by building one in lightning speed

Posted on

There’s a lot to be gained by deeply studying javascript syntax, how HTML & CSS works, etc, but at the end of the day–we’re here to build. You might not need as much JavaScript as you think to hit the ground running. In fact, you can learn as you go along, just like developers do in the real world.

We’re going to go all-out here and build a simple non-realtime chat app in native JavaScript with a REST API for the backend using Express.js. We’ll even build a quick database for it in PostgreSQL. By the end of this, you’ll see how everything comes together. You might not understand it perfectly, but that’s okay. Instead of getting stuck in tutorial hell, you’re going to use what you don’t understand to fuel your studies.

We’re not here to build a beautiful UI, or even a beautiful codebase. Just a simple UI and a quick backend to show off the concepts.

At the very end, I’ll tie in what you’ve done to how webapp development works in the real world.

I recommend using VS Code to browse and edit the codebase.



Warning: you’re about to be thrown in the deep end.

Don’t give up! In fact, move on if you’ve hit too big of a wall. But also, if you haven’t even touched HTML or JavaScript yet, check out The Odin Project’s foundations course.



How quickly can we build a server?

Very. Building the foundation for a server is usually done once, so we have some tools that can generate a good one for us. My personal favorite is provided by the Express team itself: https://expressjs.com/en/starter/generator.html.

Create a folder for your project, open your terminal or command line to it, and run the following:

npx express-generator --no-view
Enter fullscreen mode

Exit fullscreen mode

Type in y if prompted to install.

Then, run npm i to install the packages that allow the server to run.

The terminal will tell you the command to run the app. Copy/paste it to run the server.
Should look something like this: DEBUG=your-project:* npm start

That’s it. Does it say Listening on port 3000? Whatever port it’s listening on, visit your browser at localhost:3000 (or your specific port).

Do you see the page? Welcome to Express!



But what’s going on inside? Where did that page come from?

Check your app.js file in VS Code. There’s a line that looks like this, go ahead and find it:

app.use(express.static(path.join(__dirname, 'public')));
Enter fullscreen mode

Exit fullscreen mode

This makes Express serve the /public folder in your codebase. You could’ve named it anything so long as it matched with a real directory in your codebase.

/public contains the HTML, CSS, and (soon!) the JavaScript for your app.

Go ahead and check out /public/index.html. It’s fairly straightforward:

<html>
    <head>
         <title>Express</title>
         <link rel="stylesheet" href="/stylesheets/style.css">
    </head>
    <body>
         <h1>Express</h1>
         <p>Welcome to Express</p>
    </body>
</html>
Enter fullscreen mode

Exit fullscreen mode

That’s where the page came from. This index.html file is the basis of your UI. You can change it into whatever you want.

Let’s turn it into a chat app!



Building a form that submits chats to the server

Keep it simple–we’re going fast here! We’ll use id’s so JS has something to work with:

<form id="chatbox">
   <label>Message
       <input type="text" id="message"/>
   </label>
   <button type="submit">Send</button>
</form>
Enter fullscreen mode

Exit fullscreen mode

So how can JS work with it? Create an index.js file in the /javascripts folder and put the following code in it–annotated in case you need to dive more deeply into the syntax:

function setEventListeners() {
 document
   // querySelector uses CSS selectors to get elements. # is for ID's
   .querySelector("#chatbox")
   // #chatbox is a form, we listen to its "submit" event here
   // Google "addEventListener js" if you'd like to learn more
   .addEventListener("submit", function (event) {
     event.preventDefault(); // keeps the page from refreshing

     // "value" is a property all inputs have in a form. for "text" inputs, it's the text
     const message = document.querySelector("#message").value;

     // learn about fetch() here: https://javascript.info/fetch
     fetch("/chats", {  // we'll have to create a /chats route in the server
       headers: new Headers({'content-type': 'application/json'}), // important!! we want to send things as JSON
       method: "post", // Google 'HTTP verbs' for more, you'll see it in the server
       body: JSON.stringify({ message }), // turns the JSON into a string for the server to parse
     })
       // fetch creates a promise. We chain .then after it for when the fetch is finished
       // Google "promises js" to learn more
       .then(function () {
         // clear it after using that same value property!
         document.querySelector("#message").value = "";
       });
   });
}

// the HTML needs to load before we can grab any element by ID!
// this will call the setEventListeners function above when DOMContentLoaded occurs
document.addEventListener("DOMContentLoaded", setEventListeners);
Enter fullscreen mode

Exit fullscreen mode

If anything in that JS file doesn’t make sense after reading the comments, Google it or use javascript.info to learn more.

We can’t forget to include this script in our index.html file.
Here’s how your <head> section in index.html should look for this to work:

<head>
    <title>Express</title>
    <link rel="stylesheet" href="/stylesheets/style.css">
    <script src="javascripts/index.js"></script>
</head>
Enter fullscreen mode

Exit fullscreen mode



What do we do with the server?

We need a way to receive POST requests at the /chats route to match with our fetch call. The body will have a JSON object { message: ‘this is the chat’ }, so we need to take that message and store it. Ideally, we want to do this inside 15 minutes. We’re just storing a message! Nothing fancy at all.



Real quick–what’s a route?

Routes handle the GETs, POSTs, and basically any incoming communication to the server.
Take a look at that /routes folder. We’ve been given index.js and users.js, but if we check out the route in users.js…it’s not that different from index.js:

/routes/users.js

/* GET users listing. */
router.get('/', function(req, res, next) {
 res.send('respond with a resource');
});
Enter fullscreen mode

Exit fullscreen mode

/routes/index.js

/* GET home page. */
router.get('/', function(req, res, next) {
 res.render('index', { title: 'Express' });
});
Enter fullscreen mode

Exit fullscreen mode

Ignore the res.render vs res.send. We see router.get(‘/’ …) for both of them. Wouldn’t they route to the same thing? Shouldn’t the Users route at least say ‘/users’?

Check out how these routes are actually hooked up to the server in /app.js:

var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');

.
.
.

app.use('/', indexRouter);
app.use('/users', usersRouter);
Enter fullscreen mode

Exit fullscreen mode

There we go. They’re imported using require and then app.use sets the root path for the route. usersRouter gets ‘/users’, and so any fetch made to the ‘/users’ path goes through it.

Our server needs a ‘/chats’ route, so let’s set that up.



Route setup

First, we need a /routes/chats.js route file. Add that under the routes folder and add this to it:

var express = require("express");
var router = express.Router();

router.post("/", function (req, res, next) {
 // destructuring used here, it's the same as req.body.message
 // Google the concept, it's convenient!
 const { message } = req.body;
 console.log(message);
 res.sendStatus(200);
});

module.exports = router;
Enter fullscreen mode

Exit fullscreen mode

Router.post? Yep. I’ll explain POST vs GET in more depth soon. In the meantime, notice how our fetch call in the index.js file used a “post” method. These must match for the server to know what to do.

Once the route is reached, we grab the message from the request body using destructuring (check out javascript.info for more on destructuring).

res.sendStatus(200) will send an HTTP 200 status code back to our original fetch request once it’s complete. That means everything turned out fine. You may have heard of the “404” error by now–it means a resource wasn’t found. There are other errors like 500–which means the server is broken. Status messages help the client know whether a request failed, worked just fine, didn’t exist, and more.

But wait, the route isn’t ready to use yet!

Let’s hook up this route first. In app.js, import this file the same way we imported the other routes. I’ll add it to that area so you see what I mean:

var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
var chatsRouter = require('./routes/chats');
.
.
.
app.use('/', indexRouter);
app.use('/users', usersRouter);
app.use('/chats', chatsRouter);
Enter fullscreen mode

Exit fullscreen mode

Now we can use that form and see that the route works. Refresh your server (turn it off and then on again with ctrl+c and then the start command!) and send a message.

You should see this on the terminal:

[the chat message you sent]
POST /chats 200 3.424 ms - 2
Enter fullscreen mode

Exit fullscreen mode

See that 200? Same status that we sent. If the message shows up too, you’re all set. Great work. We’ll send something back to the client once we’ve stored data in our database.



So how do we store this message?

Setting up a PostgreSQL database isn’t that much work, and it’s one of those things that takes no prior knowledge to do. The hardest part is usually trying to remember your admin account’s username and password, so write it down somewhere–it’s just for personal use anyways.

A little bit of context to help:
The server connects to the database. It doesn’t run the database itself. We’re setting up something that runs entirely outside of Express.

Install PostgreSQL here: https://www.postgresql.org/download/

It will come with a program called pgAdmin, which provides a UI to manage your databases. It’s very handy, so open it up once it’s all ready.

You’ll need to create a database using the UI. Here’s a screenshot of what to click on:
Creating a database in pgAdmin

Give the database a useful name like “chat-app” and hit save:

Share this:

Leave a Reply

Your email address will not be published.