Dreamfest is a music festival, and the planning team has already built the UI and the routes, as well as having designed and seeded the initial database tables. We'll implement the database functions to be used from the routes, allowing the planning team to manage locations and events.
Full overview
You've just landed your first dev role and you're responsible for creating an app that manages DreamFest, a wholesome three day festival that offers attendees daily yoga and meditation, arts and crafts, healthy eateries, wellbeing workshops and sweet beats.
Your app needs to give the festival organisers the ability to add locations and to add events at those locations. As plans change, they will also need to be able to add, edit and delete events.
Fortunately, the team has already confirmed the venue and dates so they know how many locations they need. They have also confirmed some partners and bands so they can begin slotting them in when your app is ready. The current planning has been prepared as seed data for your database.
The design team has worked up the UI and routes, but they haven't yet connected them to the database. That's where you come in. You'll implement the database functions to be used from the routes.
Let's get stuck in!
-
Clone this repo and
cdinto the new directory -
Install packages, run migrations and seeds, and start the dev server with
npm run devTip
Commands might look like this:
npm i npm run knex migrate:latest npm run knex seed:run npm run devThis will create and populate the database with the existing migrations and seeds, and start the server.
-
Get familiar with the current state of the app and the existing codebase
The application is usable... ish. You can try anything and the app shouldn't break or throw any errors, but adding, editing and deleting events and locations doesn't work yet. Also, you're only seeing hard-coded data. Most of the changes you'll need to make are marked with TODO:. Be sure you understand the existing code and have a plan before you start coding new functionality.
- Have a look at the
GET /api/v1/locationsroute inserver/routes/locations.ts - Complete the
getAllLocationsfunction indb/index.tsand have it return a Promise that resolves to an array of locations from the database - Complete the route using your new database function
-
Have a look at the
GET /api/v1/schedule/:dayroute inserver/routes/schedule.js -
Build a
getEventsByDayfunction with adayparameter. Today we'll put all our database functions indb/index.ts -
Complete the route using your new database function
More about the
getEventsByDayfunction- JOIN the
eventsandlocationstables WHEREevents.location_id = locations.id - Filter (
where) the results for only events where the day matches. Remember to pass thedaywhen you call your function! - Note that the
eventsandlocationstables both havename,description, andidcolumns. How can you specify which one to use when? What is the shape of the data that the component is expecting? Hint: look at the shape of the hard-coded sample data inserver/routes/schedule.js
If some data isn't displaying in the app, try using
console.logto look at your data, so that you can compare it to the sample data- In particular, if you're sending the
dayproperty correctly, then the heading in the app should say "Events: Friday", "Events: Saturday" or "Events: Sunday". If it just says "Events:", take another look at your data!
- JOIN the
- Look at the
GET /api/v1/locations/:idroute inserver/routes/locations.ts
Tip
This route supplies the current data to the form, ready for the user to edit it.
-
Build a
getLocationByIdfunction inserver/db/index.tswith anidparameter -
Be sure the form is being populated correctly
Tips
- If it's not working, try the trouble-shooting strategies from section 2
- Can
.first()help you here?
- Submitting the "Edit Location" form should send an HTTP PATCH request which will hit your
PATCH /api/v1/locations/:idroute, inroutes/locations.ts
making a PATCH request
This component uses the `useEditLocation` hook, from `client/hooks/api.ts`, this provides a react-query mutation that makes PATCH requests to a specific location.-
Build an
updateLocationfunction inserver/db/index.tswith anupdatedLocationparameter (note the "d" in "updateD")More about the
updateLocationfunctionIf you find yourself struggling with the
updatedLocation(object) parameter, you might start by usingid,nameanddescriptionparameters instead.- UPDATE the
locationstable with the updated location details
- UPDATE the
-
Submitting the "Add New Event" form should send an HTTP POST request which will hit the
POST /eventsroute, inroutes/events.tsTips
- You likely need to rename the
locationIdproperty of the body object to belocation_idbefore passing it to the database - You may also want to ensure that
location_idhas a type ofNumberrather thanString
- You likely need to rename the
-
Build an
addNewEventfunction with aneventparameter
-
Deleting an event will send an HTTP POST request which will hit your
POST /events/deleteroute inroutes/events.tsMore about deleting an event
Within the site, you will find the delete button on the same page you edit an event
- Note that the "Edit event" page is currently displaying hard-coded details in the form (you'll fix this in the next step), but to check if this page is correct at this stage, click "Edit event" on (for example) the "Cutest Puppy Awards" card, you should then find yourself at the front-end route
/events/4/edit, 4 being the id of the event (as seen in your seeds). - The "Delete event" button should be able to delete "Cutest Puppy Awards" (id 4) even though the displayed details are for "Slushie Apocalypse I" as you will find it uses the id provided by the url, not the hardcoded data.
- Note that the "Edit event" page is currently displaying hard-coded details in the form (you'll fix this in the next step), but to check if this page is correct at this stage, click "Edit event" on (for example) the "Cutest Puppy Awards" card, you should then find yourself at the front-end route
-
Build a
deleteEventfunction with anidparameter
More about editing events
Show the form
- Look at the
GET /events/:idroute inroutes/events.ts. This route supplies the current data to the "Edit Event" form, ready for the user to edit it. - Build a
getEventByIdfunction with anidparameter. Use this in your route.
Update the form
- Like the "Add new event" form above, the "Edit event" form also needs a list of locations from the database. We can use
getAllLocationsfor a third time, but this time we need to modify the data before we send it to the form, so that our data records which location is the current location for this event- Maybe you could use an array function here?
- Make sure you call
getEventByIdfirst, and thengetAllLocations- You're managing three bits of data here:
days,eventandlocations, how will you manage this data so that each function in the promise chain can see everything it needs to see?
- You're managing three bits of data here:
Submit the form
- Build an
updateEventfunction with anupdatedEventparameter - Update
PATCH /events/:idinroutes/events.ts
More about adding locations
You'll need to create new things in this step, but referring to existing features will help.
Show the form
- In
client/components/LocationsList.tsx, create an "Add Location" link (similar to the "Add Event" link inclient/components/DaySchedule.tsx) - Create a new component at
client/components/NewLocation.tsx- Look at
client/components/EditLocation.tsxandclient/components/NewEvent.tsxfor guidance
- Look at
- add a client-side route in
client/routes.tsxso that/locations/addshows our new component
Submit the form
- Create a
POST /api/v1/locationsroute inroutes/locations.ts - Build an
addNewLocationfunction with alocationInfoparameter - Create a hook with
useMutationto connect ourNewLocationcomponent to the API
- Refer to
client/hooks/use-create-event.tswhen writing your hook function
More about deleting locations
You'll need to create new things in this step too, but referring to existing features will help.
Create link
- Add a new "Delete" form and button to
client/components/EditLocation.tsx(seeclient/components/EditEvent.tsx)- Pass the
idas a hidden form field
- Pass the
Create route
- Create a
DELETE /api/v1/locations/:idroute inroutes/locations.ts - Build a
deleteLocationfunction with anidparameter _ If you delete a location that has an event, what happens to the event? Why?_
More about testing helpers
Some tests have been created in helpers.test.ts but they haven't been written yet. They are just testing the functions exported from helpers.ts so they should be pretty easy (as far as testing goes). Some of the functionality hasn't been implemented in the helper functions, so you'll need to do that too. Perhaps this is a good time to revisit test-driven development (write the tests before implementing the functionality in helpers.ts). Remember red, green, refactor! * Note that the validateDay function will use a days parameter if one is supplied, or if not then it will use the hard-coded eventDays value (similar to db = connection that you've been using in your functions)
Optimising database queries
With database queries, it's often most efficient to ask for only the data you need. Take a look at getAllLocations, and you might notice that selecting all fields will include the description data. But the description data for the full set of locations is only used by the showLocations.hbs view. Every other time we call getAllLocations the description is not used.
Consider writing a separate db function (perhaps getAllLocationsWithDesc?) to request the complete data when needed, and updating getAllLocations to request only the necessary fields in all other cases.
Optimising images
Another way to improve the performance of a site is by making sure images are fit for purpose. Saving images to the best format and resolution will ensure you don't unnecessarily slow down the experience for users. For more detail, check out this resource.
Linting and formatting
We've already put in place automatic tools that help to format your code in a consistent way. This helps others read your code and makes your life easier, too! If you've ever noticed that whitespace or quotes change when you save a file, you've seen Prettier in action. In addition, we've set up a set of "lint" rules which may have caused angry-looking underlines on your code. You can explicitly cause eslint to run and output errors and warnings by running npm run lint from the command line. Fix any problems it informs you of, and your code will be that much cleaner.
Separation of concerns
Separation of concerns is the idea that a function, component, or file should have a single responsibility. Having CSS in a separate file from your HTML is one early example. You will likely have been doing this without thinking too much about it, but check to make sure that your functions and files aren't doing too much. If you find a function that's doing several things, refactor it! Separation of concerns helps keep code maintainable and testable.
Follow the guide to add a many-to-many relationship