Let's add a simple "user account" system so we can implement task assignments later. This will be an extremely basic user implementation for the purpose of this guide.
The User
schema should have the following fields:
name
: string, requiredprofilePictureURL
: string, optional
Note: In a real application, we would probably want some kind of image- or file-hosting service to store user profile pictures (such as Firebase Cloud Storage or AWS S3). For this guide, we'll just directly paste URLs when creating users.
- Create a new file
backend/src/models/user.ts
. - Use
models/task.ts
as a guide to define and export the newUser
schema with Mongoose. - Create a new file
frontend/src/api/users.ts
. - Use
api/tasks.ts
as a guide to define and export aUser
interface matching theUser
schema. (Remember to include the_id
field.)- Since User objects are simple enough to be directly encoded as JSON, we don't need to define a separate
UserJSON
interface andparseUser
function.
- Since User objects are simple enough to be directly encoded as JSON, we don't need to define a separate
Creates a new User object in the database with the fields provided in the request body, then returns the new User. If the request body doesn't contain valid User data, returns a 400 error.
Example request:
POST /api/user
{
"name": "John Doe",
"profilePictureURL": null
}
Example response:
201
{
"_id": "64ebc766fa1e11e2d987a8e9",
"name": "John Doe",
"profilePictureURL": null
}
-
Create a new file
backend/src/controllers/user.ts
. -
Use
createTask
fromcontrollers/task.ts
as a guide to define a new Express request handlercreateUser
.export const createUser: RequestHandler = async (req, res, next) => { // ... };
-
Create a new file
backend/src/validators/user.ts
. -
Use
createTask
fromvalidators/task.ts
as a guide to define a newexpress-validator
validation chain.export const createUser = [ // ... ];
-
Create a new file
backend/src/routes/user.ts
. -
Use
routes/task.ts
as a guide to define and export a new Express router for user-related requests. -
Modify
backend/src/app.ts
to import and use the router you just created to handle routes starting with"/api/user"
. -
Test your implementation by using Postman or running the following command a few times with different values:
curl -X "POST" http://127.0.0.1:3001/api/user \ -H "Content-Type: application/json" \ -d '{"name":"<Some name>","profilePictureURL":"<Some URL to an image>"}'
Feel free to use the example profile pictures that we've provided in the
frontend/public
folder. The URLs for these would be, for example,/profile1.png
(including the leading slash).You should be able to see your new users in mongosh:
use todoList db.users.find()
Note: We won't implement user creation in the frontend, so there's no need for an API client function in frontend/src/api/users.ts
which corresponds to this route.
Returns the User object with the provided ID. If no such User exists in the database, returns a 404 error.
Example request:
GET /api/user/64ebc766fa1e11e2d987a8e9
Example response:
200
{
"_id": "64ebc766fa1e11e2d987a8e9",
"name": "John Doe",
"profilePictureURL": null
}
- In
backend/src/controllers/user.ts
, add another request handlergetUser
. UsegetTask
fromcontrollers/task.ts
as a guide. - Add the new route to
src/routes/user.ts
. - Test your implementation by using Postman or running the following command with some different IDs:
Try a couple User IDs that you got from mongosh, as well as some Task IDs and some nonexistent IDs.
curl http://127.0.0.1:3001/api/user/<id>
- In
frontend/src/api/users.ts
, add an API client function that makes requests to this route. UsegetTask
fromapi/tasks.ts
as a guide.
The Task
schema should have the following additional fields:
assignee
: ObjectId, optional, ref to a Task document- For simplicity, validation of this field is not necessary. That is, we don't need to check whether it's a valid ID of an existing User object.
-
In
backend/src/models/task.ts
, update theTask
schema with the newassignee
field. Be sure to use the ObjectId type from Mongoose and make it aref
to the'User'
schema. -
Update all API routes that return a Task object to populate the
assignee
field with the corresponding User object. This means Mongoose will automatically replace the ID with the actual object in the return value, or null if it doesn't exist. You should updategetTask
,createTask
,updateTask
, andgetAllTasks
inbackend/src/controllers
.- For
createTask
, you may need to run a query for the newly created Task so you can populate it.
🤔 For new developers: Populating objects
Populating sub-objects isn't always necessary; it depends on your specific needs for each API route. In our case, User objects are very small, so it's convenient to just always include the assigned User within a Task—then we won't need to send a separate request to retrieve the User itself.
- For
-
In
frontend/src/api/tasks.ts
, add the following field to both theTask
andTaskJSON
interfaces:assignee?: User;
where
User
is imported fromapi/users.ts
. -
In the same file, add the following field to
CreateTaskRequest
andUpdateTaskRequest
:assignee?: string;
This is a string instead of a
User
because we only want to send the ID, not the entire User object. -
Update the
parseTask
function to include the assignee.
⚠️ Caution: Updating validators
If we did want to validate the assignee
field, we would have to update backend/src/validators/task.ts
as well. Don't forget to do this in your real projects!
Remember to add, commit, and push your changes!
Previous | Up | Next |
---|---|---|
2.0. Prepare for development | Part 2 | 2.2. Implement the task detail page |