Congrats, you've completed the Countability backend! Now it's time to make an interface that your users will be able to interact with in A6: Countability frontend. Make sure to read this document fully as well, as it contains a lot of A6-specific info!
This starter code implements posts, feeds, and forms with no styling. The backend starter code for posts and users from the previous assignment (A5) is contained in the server
folder. The frontend starter code is in the client
folder and is implemented using the Vue framework.
The project is structured as follows:
api/index.ts
sets up the backend database connection and Express server. This should actually be in theserver
folder, but it must be here due to a Vercel limitation.server/
contains the backend starter code from A5 (with some changes)post/
contains files related to the Post conceptuser/
contains files related to the User concept
client/
contains the frontend starter codeApp.vue
is the root component of your applicationmain.ts
is the entry point of your application, which initializes Vuecomponents/
contains the components of the frontendAccount/
contains the account settings page and the related formsPost/
contains the homepage and components related to PostsLogin/
contains the login/register page and the related formsCommon/
contains general form components that can be reused across different concepts
public/
contains base HTML files and static assets (like the default Countability logo)router.ts
contains the Vue routerstore.ts
contains the Vuex store, which stores application state and persistent data
Make a copy of this repository under your personal GitHub account by clicking the Use this template
button. Run npm install
in your terminal to install local dependencies. Copy your .env
file from A5 into the root directory of your new repo. Make sure you can run the starter code locally before proceeding.
To incorporate the backend you developed in A5 with our starter frontend, please move your files into the server
folder. For example, note that whereas the post
and user
folders were in the root directory of your backend, they have been now moved to server/post
and server/user
, respectively.
We've made some updated to the A5 server starter code that we hope you can incorporate into your backend as well.
Please reference the full diff here for a complete list of changes.
A summary of the changes is provided below:
api/index.ts
:- Add mongo store to track sessions
- Remove old frontend from express server (remember to copy over lines importing your
router.ts
files)
post/middleware.ts
anduser/middleware.ts
:- change all contents of
error:
to strings to be easily printed out by the frontend - in
isUsernameNotAlreadyInUse()
ofuser/middleware.ts
: bug fix related to changing password
- change all contents of
post/collection.ts
:- update finding posts from an author to return in descending order for consistency with finding all posts
post/router.ts
- updated incorrect documentation for
GET /api/posts
- changed
PUT
toPATCH /api/posts/:postid
, to better follow REST API conventions
- updated incorrect documentation for
user/router.ts
- add
GET /api/users/session
so the frontend can fetch info about the logged-in user - changed
PUT
toPATCH /api/users
, to better follow REST API conventions
- add
user/collection.ts
- add typings to a
updateOne()
parameter to make TypeScript happy
- add typings to a
Once you're done, test once more that you can run the project locally. Now you're ready to start developing your frontend interface for Countability!
Running locally requires a few extra npm scripts from package.json
in comparison to A5.
- Run
npm run serve
, which compiles the frontend for hot-reloading with webpack and serves it at port8080
. - Open a new terminal (with the original one still open) and run
npm run dev
to start the backend at port3000
. - To view your website, connect to localhost:8080 (instead of port 3000) since the backend will no longer serve any HTML files.
Vue proxies any URL it can't resolve on the client side (at port 8080) to the server (to port 3030), which is why we can call API routes using relative URLs (such as fetch('/api/posts')
). See client/vue.config.js
and associated Vue CLI docs for more details.
We will be using Vercel to host a publicly accessible deployment of your application.
-
Log in to Vercel and go to the project creation page and select
Continue with GitHub
. -
Find your frontend repository you just created and click
Import
. For theFramework Preset
, chooseVue.js
. In theBuild and Output Settings
section, toggle the override switch forOutput Directory
and set it toclient/dist
. In theEnvironment Variables
section, add an entry whereNAME
isMONGO_SRV
andVALUE
is your MongoDB secret. -
Click
Deploy
and you will get a link likehttps://Countability-starter-abcd.vercel.app/
where you can access your site.
Vercel will automatically deploy the latest version of your code whenever a push is made to the main
branch.
Working in Vue means working with Vue components. The starter code organizes components by the resultant tree structure of how the components are composed together.
Every component takes advantage of an HTML-based template syntax, which is HTML code that binds the rendered DOM to the component data. Inside the template is where we can display specific form components like <CreatePostForm />
. We also take advantage of conditional rendering here to display different things to different users (such as signed in vs. signed out). For example, in client/components/Post/Posts.vue
in lines 5-23, we have:
<section v-if="$store.state.username">
<header>
<h2>Welcome @{{ $store.state.username }}</h2>
</header>
<CreatePostForm />
</section>
<section v-else>
<header>
<h2>Welcome to Countability!</h2>
</header>
<article>
<h3>
<router-link to="/login">
Sign in
</router-link>
to create, edit, and delete posts.
</h3>
</article>
</section>
Here, if store.state.username
exists, we say Welcome @username
. Otherwise, we say Welcome to Countability!
and give them a link to the login page. This is just one example of conditional rendering.
Each .vue
file also has script tag, which is where you can export the actual component.
The "top level" components displayed when you navigating to certain URLs (like /login
or /account
) are shown in client/router.ts
. Within each of these components, we have:
name
name of the componentcomponents
components that are used in this top level component, usually forms likeLoginForm
The "lower level" components are the general form components that we have provided. These consist of:
name
name of the componentmixins
(sometimes) used to have reusable logic between components. In this case, mixins have components inclient/components/common/
likeBlockForm
.props
(sometimes) properties that are passed from a parent component to child components as neededdata()
stores data associated with this Vue instancemethods
methods associated with the current component that can be used in it
You may see that many components use this.$store
. In client/store.ts
, we have created a Vuex.Store
that is used to our application state. We use mutations to change state. We call these mutations by doing this.$store.commit('[mutation]', [payload])
. The payload
is like an additional argument that could be used in our mutation. An example mutation:
setUsername(state, username) {
/**
* Update the stored username to the specified one.
* @param username - new username to set
*/
state.username = username;
}
This mutation is called a few times, such as in App.vue
where it says this.$store.commit('setUsername', user ? user.username : null);
. In this case, we are committing the value of our username, which can be accessed within the state as $store.state.username
.
Routing on the server side means the server sending a response based on the URL path that the user is visiting. When we click on a link in a traditional server-rendered web app, the browser receives an HTML response from the server and reloads the entire page with the new HTML.
However, in a Single-Page Application (SPA) like the one we're developing, the client-side JavaScript can intercept the navigation, dynamically fetch new data, and update the current page without full page reloads. This typically results in a more snappy user experience, especially for use cases that are more like actual "applications", where the user is expected to perform many interactions over a long period of time.
In such SPAs, the "routing" is done on the client side, in the browser. A client-side router is responsible for managing the application's rendered view using browser APIs such as History API or the hashchange event. We use the Vue Router library for client-side routing, which is referenced in client/router.ts
.
IMPORTANT: This starter code uses version 2 of Vue, not version 3! There are multiple significant breaking changes between the two versions, so please only consult documentation, StackOverflow questions, and other resources that reference Vue 2.
Here is a list of documentation you may want to consult while working with Vue:
- Vue 2 main library documentation
- Vue Router for Vue 2
- Vuex for Vue 2
- Vue Template Explorer for Vue 2
- MDN's in-depth Vue tutorials
Returns
- Logged in user
- Null if no user is logged in
Throws
Body
username
{string} - The user's usernamepassword
{string} - The user's password
Returns
- A success message
- An object with user's details (without password)
Throws
403
if the user is already logged in400
if username or password is not in correct format format or missing in the req401
if the user login credentials are invalid
Returns
- A success message
Throws
403
if user is not logged in
Body
username
{string} - The user's usernamepassword
{string} - The user's password
Returns
- A success message
- An object with the created user's details (without password)
Throws
403
if there is a user already logged in400
if username or password is in the wrong format409
if username is already in use
Body (no need to add fields that are not being changed)
username
{string} - The user's usernamepassword
{string} - The user's password
Returns
- A success message
- An object with the update user details (without password)
Throws
403
if the user is not logged in400
if username or password is in the wrong format409
if the username is already in use
Returns
- A success message
Throws
403
if the user is not logged in