To create a site that will serve up Facespace! Oh, and learn a little more about Node, routing, EJS, and CSS along the way.
yarn install
yarn dev
to launch the server.
There is a file /data/users.js
that contains an array of user
s. Each user looks like this.
{
_id: '1008',
name: 'Fletcher',
friends: ['1006', '1007', '1009'], // array of the ids of user's friends
avatarUrl: '/images/profile-pics/000003.jpg',
},
The project is coming to you entirely styled! Let me repeat that: All of the CSS is done for you. ๐ฑ I know, right! But because nothing is free, you will have to figure out the classes that go with each element. You are able to achieve the same look by assigning the css classes to the right element.
The homepage should show a grid of all of the users in the system.
Create a GET
endpoint for the homepage. Update your server.js file to match this.
'use strict';
const express = require('express');
const morgan = require('morgan');
const { users } = require('./data/users');
// declare the 404 function
const handleFourOhFour = (req, res) => {
res.status(404).send("I couldn't find what you're looking for.");
};
+ const handleHomepage = (req, res) => {
+ res.status(200).send('homepage');
+ };
// -----------------------------------------------------
// server endpoints
express()
.use(morgan('dev'))
.use(express.static('public'))
.use(express.urlencoded({ extended: false }))
.set('view engine', 'ejs')
// endpoints
+ .get('/', handleHomepage)
// a catchall endpoint that will send the 404 message.
.get('*', handleFourOhFour)
.listen(8000, () => console.log('Listening on port 8000'));
Once you've added this right code, load the homepage at http://localhost:8000. You should see this.
- In
views/pages/
there is a file calledhomepage.ejs
- Add this code to that file.
<!-- note that all of our page templates will include a header and footer partial -->
<%- include('../partials/header') %>
<div class="home-page">
<h2>All Facespace members</h2>
<!-- content here -->
</div>
<%- include('../partials/footer') %>
To render the homepage, you will need to modify the handleHomepage
function. Instead of send
ing something. Let's render the homepage.ejs
file that we just created.
const handleHomepage = (req, res) => {
- res.status(200).send('homepage');
+ res.status(200).render('pages/homepage');
};
You should now see this in the browser:
First we need to pass the users data to the ejs
template. When rendering an ejs
template, we can pass it an object as the second argument that can contain anything we like. In this case, we want to pass the entire users
array.
res.status(200).render('pages/homepage', { users: users });
Once the data is available to the template, we need to loop through the array and render all of the images.
<ul class="homepage__all">
<% users.forEach(user => { %>
<li class="homepage__all--user">
<img src="<%= user.avatarUrl %>" />
</li>
<% }) %>
</ul>
You should now see this in the browser:
Let's create a profile page that will be unique to each user. Our endpoint should contain the user's _id
, which will allow us to "know" which data to use.
Create a GET
endpoint that we can use to show the user's profile page. Your endpoint should start with /users/
and end with a url param for the _id
.
This endpoint will trigger a function handleProfilePage
that you will need to create as well. For now, have that function res.send
the _id
that is in the url.
In /views/page/
, create a file called profile.ejs
. This will be the page that we render, at the endpoint we created above.
Here is the beginning of the code, you will need for this file.
<%- include('../partials/header') %>
<div class="profile-page">
<%= user.name %>
</div>
<%- include('../partials/footer') %>
Convert the res.send
you have in handleProfilePage
to a res.render
of that profile page.
Notice the temporary insertion of the user.name
into the ejs template. You can use that to confirm that your endpoint is functioning properly. Currently, it should not be working. We need to send the user data to the ejs
template.
Add the user's data to that object. You will need to figure out which user data to send based on the provided _id
in the url params.
-
Render the user's avatar, and name.
-
๐ Render the list of friends.
<div>
<h3><%= user.name %>'s Friends</h3>
<ul>
<!-- loop over the array of friends -->
<!-- each friend's image and name should be visible. -->
</ul>
</div>
Below is what the profile page should look like. While it doesn't need to be pixel-perfect, it should be close enough as to not look different from the images below. I've provided the mobile version as well.
Our security system will be total crap, but will work for our purposes. True site security is too important for us to implement ourselves! When the time comes, we'll leverage code from experts in the field.
For this workshop, we're going to simply ask for the user's first name and if it's in the users array, we'll assume that that is the person that is logging in.
In server.js
we need to have an endpoint that will receive requests for the signin page. This endpoint will call a function called handleSignin
. For testing purposes, do a res.send('ok')
in that function. This will allow us to confirm that the signin endpoint works.
In views/pages
, create a new file called signin.ejs
. Add the following ejs
code to the file. Take a few minutes to go over this code and make sure you understand what it's doing.
<%- include('../partials/header') %>
<div>
<form method="get" action="/getname">
<label for="firstName">First name</label>
<input type="text" name="firstName" placeholder="Your first name" />
<button type="submit">Submit</button>
</form>
</div>
<%- include('../partials/footer') %>
The form we've just added doesn't yet render on the /signin
page. We need to tell handleSign
to render that particular ejs
page template.
Our form looks good but it doesn't yet do anything. We need the form to send the input to the server.
Notice that the form (in signin.ejs
) has action
attribute. That is the endpoint that the form will contact when a user submits the form. Let's create a GET
endpoint that will receive the data from the form.
.get('/getname', handleName)
This type of HTML form sends the data from the form as query parameters in the req
uest.
- Define a variable
firstName
and assign the value ofreq.query.firstName
. You can also write a temporaryconsole.log()
to make sure that your function works and that you have access to the value offirstName
. - Use the
firstName
tofind()
that user's data inusers.js
. That array is already imported and available to you. (See line 6 ofserver.js
). - If it exists redirect to that user's profile page.
- If it doesn't exist, redirect to the signin page. (This is a reload of the signin page.)
We can redirect the browser to an endpoint with the following code.
res.redirect('/the-endpoint');
- You should also return a status code to the browser.
res.status(200); // when the request is successful
res.status(404); // when the request is not successful. In this case, the user was not found.
It is good practice to chain our status and our render like so:
res.status(404).render('/signin');
๐ก - Minimally complete workshop (75%) - ๐ก
We now have a functioning app! ๐ Let's add more functionality to the make it better.
We should have a link to the signin page in the header.
When the user is signed in, the "sign in" link should be replaced by a greeting and the user's name.
This will mean storing the user data in memory, and passing it to header.ejs
. You can store the current user object in currentUser
variable that is defined in the server.js
file. This will store the current user in the server memory until the server is restarted.
While you're in the header, it would be good to turn the title into a link to the homepage.
Hint: Passing data to a page template also makes it accessible all partials included that page.
- Faces on the homepage should link to that person's profile page.
- Let's also add a little UX tweak on hover. Give the image some sort of effect on hover.
When a user is signed in and looking at the homepage, it would be great if there were some visual indication as to who their friends are in the grid of faces. My example is a ribbon on the image, but feel free to do whatever you like.
If someone is already signed in, they should not be able to see the signin page. Currently, if a user signs in and navigates to http://localhost:8000/signin, they will see the sign in page.
Prevent this from happening.
๐ข - Complete workshop (100%) - ๐ข
Here are some other features that you could add to the app. None of these have any solutions.
- User can add/remove friends. This should update the friends array of both users. Being friends is reciprocal.
- If a user adds a friend, they are not automatically added. The other user needs to accept this first. It would be useful to create a new array of
pendingFriends
in the user object. - A sign up page... that does exactly what you would expect.
- What else can you think of?