You've just landed your first dev role and you're responsible for creating an app that manages DreamFest, a wholesome 3 day festival that offers attendees daily yoga and meditation, arts and crafts, healthy eateries, well-being workshops and sweet beats.
Your app needs to allow the festival organisers the ability to add locations and 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 location 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 formatted as JSON in the data
folder so you can use it as seed data for development and testing.
The design team has worked up the UI and routes, but they haven't added the data persistence layer. That's where you come in. You'll create the migrations and seeds and then implement the database functions to be used from the routes. Let's get stuck in!
๐ indicates an assessment opportunity
๐ช indicates a stretch opportunity
- ๐ Setup
- ๐ Create migrations
- ๐ Add seed data
- ๐ Show all locations
- ๐ Show events for a day
- ๐ Edit locations
- ๐ Add new events
- ๐ Delete events
- ๐ช Edit events
- ๐ช Add new locations
- ๐ช Delete locations
- ๐ Test helper functions
FYI: A note about styling
- Clone this repo
cd dreamfest
npm install
- note: if install fails, run
npm uninstall sqlite3
,npm install
thennpm install sqlite3
npm run dev
- Have a little play with the app as it is
- Get familiar with 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. You will need to write and call all of the necessary database functions. We recommend doing this in a new db/index.js
file. You will call these database functions from the routes. All of the changes in routes
are marked with TODO:
. Be sure you understand the existing code and have a plan before you start coding new functionality.
But first, you'll need to create the database.
Knex has been installed, the knexfile.js
has been created and a knex
script is waiting patiently for you in package.json
. So be sure to always use npm run knex ...
and not npx knex ...
.
You'll need to create 2 tables: locations
and events
id
: number (primary key)name
: stringdescription
: string
id
: number (primary key)location_id
: number (foreign key to locations.id)day
: stringtime
: stringname
: stringdescription
: string
- Create the migration files for these 2 tables and apply them using
npm run knex
. - Use a database tool, such as the DB Browser for SQLite, to verify they were created correctly.
- Create the seed files based on the JSON files in the
/db/data
folder. - Add the seed data to the database using
npm run knex
. - Use a database tool to verify the data was added correctly.
- Have a look at the
GET /locations
route inroutes/locations.js
. - Write a
getAllLocations
function indb/index.js
and have it return a Promise that resolves an array of locations from the database. - Complete the route using your new database function.
- Don't forget to put the
viewData
andres.render
call in your callback once you have the locations from the database.
GET /schedule/:day
inroutes/schedule.js
getEventsByDay(day)
indb/index.js
- JOIN the
events
andlocations
tables WHEREevents.location_id = locations.id
- Filter (
where
) the results for only events where the day matches
Show the form
GET /locations/4/edit
inroutes/locations.js
getLocationById(id)
indb/index.js
- Be sure the form is being populated correctly
Submit the form
- Happens in
POST /locations/edit
inroutes/locations.js
updateLocation(updatedLocation)
- UPDATE
locations
table with updated location details - Be sure
res.redirect('/locations')
is inside your.then
function
- Happens in
POST /events/add
inroutes/events.js
addNewEvent(event)
indb/index.js
- Be sure
res.redirect('/schedule/:day)
is inside your.then
function
- Happens in
POST /events/delete
inroutes/events.js
deleteEvent(id)
indb/index.js
- Be sure
res.redirect('/schedule/:day)
is inside your.then
function
Fix form
- Happens
GET /events/add/:day
inroutes/events.js
- Insert
getAllLocations()
in your route - Be sure
res.render('editEvent', viewData)
is inside your.then
function
Show form
- Happens in
GET /events/:id/edit
inroutes/events.js
- Create
getEventById(id)
andgetAllLocations()
indb/index.js
- When calling these functions in your route, call them in this order. Consider returning the event's
locationId
to the next function in the promise chain.
Submit form
- Create
updateEvent(updatedEvent)
indb/index.js
- Update
POST /events/edit
inroutes/events.js
You'll have to create new things in this step, but referring to existing features will help.
Show form
- Create link to "Add location" in
views/showLocations.hbs
- Styling will be very similar to the "Add event" link in
views/showDay.hbs
- Styling will be very similar to the "Add event" link in
- Create
views/addLocation.hbs
file (very similar toviews/editLocation.hbs
) - Create a
GET /locations/add
route inroutes/locations.js
to renderviews/addLocation.hbs
Submit form
- Create
POST /locations/add
inroutes/locations.js
- Create an
addNewLocation(locationInfo)
indb/index.js
res.redirect('/locations')
You'll also have to create new things in this step, but referring to existing features will help.
Create link
- Add new "Delete" form and button to
views/editLocation.hbs
(seeviews/editEvent.hbs
)
Create route
- Create a
POST /locations/delete
inroutes/locations.js
- pass the
id
as a hidden form field
- pass the
- Create and export a
deleteLocation(id)
indb/index.js
res.redirect('/locations')
Some tests have been created in helpers.test.js
but they haven't been written yet. They are just testing the functions exported from helpers.js
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.js
). Remember red, green, refactor ๐
The build
script (in package.json
) creates a production build, which creates a very small public/main.css
. If you are not going to use any class names that aren't already in use, you only need to run this once (it is run as a postinstall
script so it has probably already run). You'll also need to run this if you change tailwind.config.js
or public/source.css
.
If you plan to do a lot of styling work, you should run npm run build:dev
, which gives you all of Tailwind CSS, but also a large public/main.css
. Just be sure to npm run build
when you're all finished if you're going to deploy this.
Note:
main.css
is in the.gitignore
so it isn't checked into the repo. It's a good practice to not commit files that are built, such asmain.css
, into Git repositories.
Also, TailwindCSS uses low-level utility classes. A lot of them have been grouped into regular CSS classes using Tailwind's @apply
feature. If you're curious, you can find them in public/source.css
in the @layer components
section. You'll need to npm run build
if you change anything in this file.