Learn MERN and Get a Head Start At Your Next Hackathon
Summary
If you’ve ever attended a hackathon, you’re familiar with the excitement that comes with learning new technologies and sitting in a room full of passionate developers. This is a great environment for testing your skills and learning new ones, but with only 24 hours to create something breathtaking, where do you start?
Don’t start at the beginning.
Not the very beginning at least. The most tedious part of starting a hackathon project is setting up your environment and connecting all the pieces together (frontend, backend, and database). To help hackers bypass this boring step, we at Asterisk have created an open-source template to allow you to hit the ground running come hacking hours. Feel free to switch from the main
branch to the feature-complete
branch if you want to avoid writing the code yourself.
The hackathon-starter template is designed to be a simple, yet thorough, starting point to help you maximize your development speed. It features a simple file structure to minimize the amount of “boilerplate” code you need to write and it is written entirely in TypeScript to minimize the number of languages your team needs to learn.
Bonus: TypeScript comes with the added benefits of being less error-prone than JavaScript and very well supported and documented in online communities.
First Things First
Before diving into the code, you must first make sure MongoDB is installed and set up. To install MongoDB Atlas, follow the Getting Started Guide on MongoDB’s website.
With MongoDB Atlas installed, you can now create a new database for your project by running the following commands.
$ mongo
$ use hackathon-starter
The mongo
command connects you automatically to your local database. The use hackathon-starter
command connects to the hackathon-starter database, or in this case, creates and connects to a new database. By default, your project will be looking for a database named “hackathon-starter”, but you are not required to use this name.
Set Up Your Project
Creating your project repository on Github is super easy with the use of templates. To get started, navigate to the hackathon-starter repository and click “Use this template.” From here you can name your repository and it will be copied to your Github automatically! Now you’re ready to clone your new repository.
$ git clone <my-repository-url>.git
$ cd <my-repository>
If you’re unfamiliar with the git CLI, check out the basic git section of the Git Handbook.
The last part of the setup is installing the dependencies. Hackathon-starter uses Yarn to manage its dependencies. In both the backend and frontend folders, you’ll find a package.json file which shows all of the external packages your project needs in order to work. For more information on Yarn and package.json take a look at the Yarn Documentation.
To install the dependencies defined in package.json, run the following command in both the frontend and backend folders:
$ yarn
This will automatically download all dependencies into a node_modules folder as well as create a yarn.lock file, which can safely be ignored.
Now that your database is set up, your project is downloaded and your dependencies are installed, you can finally launch your web application! Open a terminal in both the backend and frontend folders and run the following command in each.
$ yarn start
After a moment, your application will launch and you should see this webpage. You’re now ready to start bringing your idea to life!
Bringing Your Idea to Life
For illustrating the process of adding features to this project, you will be creating a revolutionary product: another To-Do app. With this golden-ticket startup idea in mind, it’s time to explore the implementation of this feature, from backend to frontend.
The Backend
Before your to-do app is ready to serve the absent minds of the world, you must first design a way to create and store your tasks; this starts with the backend.
Open the backend/src
folder to start exploring the file structure. You’ll notice the backend is broken up into three separate sections along with the entry point, index.ts. Starting with the entry point, you’ll be presented with the following code.
The sole responsibility of this file is to initialize the server (line 19). Take a look at the comments above to get an idea of what each part does. Currently, there is only one route in use: /todo, which uses todoRouter
to handle all requests made on it.
The Routing Layer
What is todoRouter
? Opening up todo.ts in the routes folder, you’ll find the answer. As mentioned before, todoRouter
handles all requests on the /todo route. It does this by defining a handler for various “subroutes” (e.g. /todo/:id/uncomplete). Add the following POST
handler into src/routes/todo.ts.
In this example, todoRouter
is defining a function, called a request handler, that will be called when you make a request to the endpoint /todo/:id/uncomplete. Note that any part of the path preceded with a colon (e.g. /:id) denotes a variable. You’ll notice that this variable is retrieved on line 6.
Looking at the body of this function, you can identify two main steps: calling the “business logic” and returning a response to the user. Connecting to the database, performing calculations or making decisions is not the concern of the request handler. This is intentional as separating concerns in your application is a great way to keep your code scalable and easy to read. To perform the business logic, the routing layer depends on the service layer.
The Service Layer
In the services folder, open the file todo.ts. Following the example from above, add the uncompleteTodo
function below in src/services/todo.ts; make sure to go back and import this function in routes/todo.ts. This function is the workhorse that performs the task of marking your to-do item as incomplete.
Although it’s a small example, this is where the magic happens. In the fifth line, the “todo” collection is referenced in the database and is immediately used to update a to-do item in the following line. If the update is not successful, this function will throw an error which will be caught by the routing layer and returned to the user in a friendly format.
With the help of the useCollection
function from db.ts, this logic is able to stay short and concise. The db.ts file contains a set of functions which provide a convenient way to connect to the database and reference collections. For more information on the MongoDB npm package, check out the npm documentation.
The Frontend
With your new API endpoints up, you’re finally ready to make this feature available on the frontend. The frontend for this template is a typical React + Redux project. This means you will be creating your webpages using React, and you will be storing the data needed by those pages in Redux. This section will assume you have some familiarity with these two libraries, but you can check out the React and Redux documentation if you need a crash course!
Diving into the frontend folder, you’ll find four distinct sections: components, models, pages, and store. The components folder is where all components which are not webpages live (e.g. Navbar, Footer, Layout). The models folder stores only type definitions such as the interface for your TodoItem
model. In the pages folder, you’ll find all components which represent a single page or route in the application. Lastly, the store folder contains the logic for the Redux state.
In the root of these directories, you’ll find the entry point of your app: App.tsx. Start with this file to get an understanding of how this app operates.
The first detail to take note of is the two components that wrap the entire component: Provider
and Router
. On a high level, the Provider
component is what allows your app to access the global Redux state while the Router
component allows your app to resolve urls and create routes. The Switch
component is responsible for resolving the route in the browser and choosing a component to render.
How does the Switch
know what to render? Notice that there is a Route
component being rendered for each item that comes from routes.ts. Here, the definitions from routes.ts are used as props for the Route
. For more information on how routing works in React, take a look at the react-router documentation.
Routes
For the purposes of a hackathon project, the basics of routing should be sufficient. Take a look at routes.ts to see how your to-do page is found and rendered.
In this example there are only two routes: the root route (“/”), and the todo route; the root route renders the HomePage
component while the todo route renders the TodoPage
component. This configuration is exported as a list of RouteProps
which is then injected into individual Route
components in App.tsx.
Pages
In the example above, you’ll notice that the todo route causes the TodoPage
component to be rendered. This component is where you will create your UI. There’s a lot to digest in this file, so take it one step at a time!
This first section is where you will define all of the CSS (styles) that make your component look beautiful. Because this template comes with Material UI by default, this should be really easy!
In this snippet, React Hooks are used to create an instance of dispatch
(for using Redux) and fetch the specific data we want from Redux.
The useEffect
function is a React Hook which allows you to execute code when a component initializes or updates. In this case, fetchTodos
will be dispatched when the component loads for the first time.
The addTodo
and checkTodo
functions exist to dispatch actions to Redux for you. Doing so will trigger the API call and cause the state to be updated! Exciting right? Updating state will be covered in-depth in the next section.
Lastly, the JSX is returned to render the component. In order to add the ability to uncomplete an item, you need a button to press. The “complete todos” section of the JSX is a great place to add this.
Notice that as it stands, this section returns a p
tag for each to-do item in completeTodos
. To add your button, make the following change.
Oh no it broke! Don’t fret, add the uncheckTodo
function below checkTodo
to fix the error.
It still doesn’t work! Although your component is complete, the uncompleteTodo
function does not exist. This is where Redux comes into play.
Redux
As mentioned before, this project uses Redux to manage state throughout the application. Redux is a state management solution that records data as a sequence of actions. These actions are essentially records of events that occur in the app and are saved in a “database” known as the store; actions can be dispatched (sent to Redux) in order to update the state. Redux is typically implemented as a wrapper around App.tsx, allowing it to take effect in all child components.
The infrastructure for your state starts in store/index.ts.
To break down what’s going on here, first take a look at line 7. The Action
type encompasses all types of actions that can be dispatched to Redux. As mentioned before, an action is essentially a record that is sent to Redux and allows the store to figure out how it should change the state of your app.
On line 9 is where the main reducer is created (more on reducers later). The combineReducers
function takes an object full of reducer functions and combines them together into one (hence the name). In this example there is only one reducer (todo), but this file is set up to allow more reducers to be easily added.
On the very next line, line 10, the State
type is defined. The purpose of this type is to provide a definition for the entire state of the application. This will make your text editor easier to work with as it will point out any type of error you make while accessing and updating the state.
In the last line, the Redux store is exported from index.ts, where it will be used by the Provider
component in App.tsx. The responsibility of the store is to execute the reducer every time an action is dispatched and keep the global state up-to-date.
The call applyMiddleware(thunk)
adds the Thunk middleware to Redux, allowing you to dispatch asynchronous actions (which are synchronous by default). You’ll need this in order to make API calls.
At this point, no real state-management logic has been added, but that changes when you look at store/todo.ts. Here lies the meat of your to-do state. This file can be broken up into five important parts, each of which will be represented below with an example.
The first part is the TodoState
. This is an interface which defines what the state should look like for tracking to-do items. You’ll notice it consists mostly of variables for loading state and errors while the to-do items themselves are kept in the todos
array.
Following TodoState
, there exists another set of interfaces: the action types. Each action type defines the type and payload for an action which can be dispatched to Redux. In this example, when the “TODO_CREATED”
action is dispatched, it is expected to come with a TodoItem
in the payload. Add the following two action types below TodoCompleted
.
The third, and most important, part of the to-do state is the reducer. The “reducer” sounds complicated, but is really just a function. It is a special function which takes a copy of the state as one argument, an action that is dispatched as the second argument, and returns a new state object. This is how the actions you dispatch update the state! Add the following cases to the reducer function to allow for uncompleting to-do items:
Notice that in each switch
case, a new state object is returned; however, it is slightly mutated based on the type of action that is provided. If the “TODO_UNCOMPLETED_STARTED”
action is provided, a new state is returned with isUncompletingTodo
set to true
. If “TODO_UNCOMPLETED”
is provided, then the state is returned with the updated to-do item.
Directly below the reducer, you’ll find the action functions. Thanks to Thunk, these are dispatchable functions which in turn can dispatch their own actions to update the state. They also request your to-do items from the backend. Add the uncompleteTodo
function below.
In the second line, you’ll see that an action is being dispatched immediately which will update the state to reflect the loading status of the API call. Once the API call returns, another action is dispatched based on the result. If the result is not successful, a “TODO_ERRORED”
action is dispatched, otherwise the “TODO_UNCOMPLETED”
action dispatches along with the updated to-do item.
The last section is the selectors. Like the reducers, selectors are just functions.
The difference is that they take the state object as an argument and return a very specific part of it rather than a brand new state. Think back to TodoPage.tsx where these selectors are used to get the complete and incomplete to-do items.
Test It Out
With the additions to Redux in place, you’re ready to fire it up! Launch the backend and frontend by running yarn start
in each folder and take a look at the result. If all goes well, you should now be able to navigate to http://localhost:3000/todo on the frontend, add to-do items, mark them as complete and… mark them as incomplete! 🎉
Congratulations! With your life-changing feature complete, you’re ready to jump to light-speed at your next hackathon!
Your project is set up and ready to go, but you still have a long 24-hour journey ahead before you win that Nintendo Switch. Check out the “More Resources” section for more ways to take your hacks to the next level and learn more about the technologies used in this project.
More Resources
Written by Brayden Cloud, Software Engineer