For this challenge, you will add Folders to the Noteful app, associate the notes with the folders, create routes and tests for the folders and update the routes and test for the notes.
- Create folder schema, update notes schema and seed the database
- Create folder endpoints and update notes endpoints
- Update
server.js
mount folder router - Update the client
Your first challenge is to create a Folder Schema and Model, update Notes Schema with folderId
and then seed the database.
Since you created a similar schema and model for Notes in an earlier challenge we will keep the instructions brief.
- Create a
/models/folder.js
file for the Schema and Model. - Create a simple
folderSchema
and define aname
field with the following criteria:- Type is
String
- Required is
true
- Unique is
true
- Type is
- Create timestamps fields
createdAt
andupdatedAt
using the Mongoose timestamps option - Configure the Mongoose
toObject
option to transform the doc:- Include
virtuals
likeid
to show in the output - Suppress the
__v
version key - Delete the
_id
from the return object
- Include
- Create
Folder
model using thefolderSchema
and export it
Now that you have defined a Folder Schema and Model, you can add a folderId
field that reference to the Notes Schema. This field informs Mongoose that there is a relationship between Notes and Folders.
In /models/note.js
, update the noteSchema
as follows:
- Define a
folderId
field:- Type is
ObjectId
- References
Folder
model
- Type is
folderId: { type: mongoose.Schema.Types.ObjectId, ref: 'Folder' }
Next you will create Folders seed data and update Notes seed data with folderId
references, then modify and run /utils/seed-database.js
to update your database with the new sample data.
- Create
/db/seed/folders.json
file - Add seed data like the following example.
[
{
"_id": "111111111111111111111100",
"name": "Archive"
},
{
"_id": "111111111111111111111101",
"name": "Drafts"
},
{
"_id": "111111111111111111111102",
"name": "Personal"
},
{
"_id": "111111111111111111111103",
"name": "Work"
}
]
- In
/db/seed/notes.json
, addfolderId
fields which reference a folder.
Below is a sample note with folderId
set to the _id
for Archive
.
{
"_id": "000000000000000000000000",
"title": "5 life lessons learned from cats",
"content": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor...",
"folderId": "111111111111111111111100"
}
Now that you have created a Folder schema and model, updated the Notes schema and created seed data, you can modify and run the seed database util to update your database.
Below are the steps followed by sample code to help you along.
- In
/utils/seed-database.js
- Require the folder model and seed data
- Add
Folder.insertMany()
and pass it the seed data - Add
Folder.createIndexes()
The Folder.createIndexes()
tells Mongo to index the Folders data immediately. The index is used enforce the unique folder names rule you created in the schema.
Normally, the index is automatically created in the background. But that leaves open a small window of time when you could, in theory, seed the database and then create a folder with a duplicate name. Calling createIndexes()
forces Mongo to create the index and prevents error from happening.
Below is a simple and reliable way to seed and index the database. The const
and require
statements, along with .then()
and .catch()
have been removed for brevity. You will need to add them in your implementation.
REMOVED FOR BREVITY
mongoose.connect(MONGODB_URI)
.then(() => mongoose.connection.db.dropDatabase())
.then(() => {
return Promise.all([
Note.insertMany(seedNotes),
Folder.insertMany(seedFolders),
Folder.createIndexes(),
]);
})
REMOVED FOR BREVITY
Finally, run the seed database util to update your database.
node utils/seed-database.js
Confirm it worked correctly by verifying the data using Mongo shell or your favorite database GUI.
Time to commit your work. Did you remember to create a feature branch?
Your next challenge is to create Folder endpoints and integration tests. Again, you've created similar endpoints and tests so we will keep the instructions brief. You can refer to your earlier work for additional details.
Create a /routes/folders.js
file for the endpoints and mount it on server.js
. In the file, create the following endpoints.
GET
all/folders
- Sort by
name
- Sort by
GET
/folders
byid
- Validate the
id
is a Mongo ObjectId - Conditionally return a 200 response or a 404 Not Found
- Validate the
POST
/folders
to create a new folder- Validate the incoming body has a
name
field - Respond with a 201 status and
location
header - Catch
duplicate key error
code11000
and respond with a helpful error message (see below for sample code)
- Validate the incoming body has a
PUT
/folders
byid
to update a folder name- Validate the incoming body has a
name
field - Validate the
id
is a Mongo ObjectId - Catch
duplicate key error
code11000
and respond with a helpful error message
- Validate the incoming body has a
DELETE
/folders
by id which deletes the folder AND the related notes- Respond with a 204 status
The current error handling process catches errors and returns them to the user. Usually this is fine, but returning database errors is considered a bad practice. Besides being a poor user-experience then might provide information that hackers can exploit. So, in .catch()
you will check the error for code 11000
, which is Mongo's code for "duplicate key error", then you will create a user-friendly error and status. Finally, call next(err)
to trigger our error handler process to return the error to the user.
- Update the appropriate query
.catch()
methods with the following code. Note, you should not update all queries, knowing/understanding which ones to update is part of the challenge.
.catch(err => {
if (err.code === 11000) {
err = new Error('The folder name already exists');
err.status = 400;
}
next(err);
});
After you have completed the rest of the challenge, comeback to this bonus challenge.
If you followed the instruction above, then deleting a folder performs a ON DELETE SET CASCADE style delete on Notes. In other words, deleting a folder deletes all the associated Notes. The challenge is to implement a ON DELETE SET NULL style delete. When a user deletes a folder you must find and $unset
all notes with that folderId. This approach removes the folderId reference but keeps the Note.
Next create integration tests to verify the Folder endpoints. Since this process is similar to the integration tests for Notes, we won't bore you with the details.
Create a /test/folders.js
file and create integration tests for Folders based on the specs above. You can use the integration tests for Notes as a reference.
You are in the home stretch, your last challenge is to update the Notes endpoints include folder info and update the tests.
Update the Notes endpoints to handle folders:
GET: /api/notes
- Check if request contains
folderId
in the querystring and add a filter which to find notes with the givenfolderId
- Check if request contains
GET: /api/notes:id
- No changes needed. Use Postman to confirm the folderId is returned in the response
POST: /api/notes
- Check if request contains a
folderId
and verify it is a valid Mongo ObjectId, if not valid respond with an error
- Check if request contains a
PUT: /api/notes
- Check if request contains a
folderId
and verify it is a valid Mongo ObjectId, if not valid respond with an error
- Check if request contains a
DELETE: /api/notes
- No changes :)
Update /test/notes.js
. You added new features to these endpoints and that functionality needs to be tested to ensure it continues to work properly.
- Update the require statements to include folders.
- Update the life-cycle hooks to seed the database with both notes and folders
- Update the existing tests to properly handle and check for
folderId
You can refer to the solution branches for "inspiration".
Finally, uncomment the folders search on public/index.js
.
Promise.all([
api.search('/api/notes'),
api.search('/api/folders'),
//api.search('/api/tags')
])
You can view an example solution and compare the differences between branches
- Solution: You can find the example solution in the
/solution/02-folders
branch in the upstream repo.