Creating a Task

In SQL, a foreign key is an attribute in a relation that also exists as a primary key in another relation.  Foreign keys allow an entity in one table to be associated with another entity in a different table.  This association often represents “ownership”, that is, an instance in one table is “owned” by an instance in another table. MongoDB is not a relational database and does not enforce foreign key constraints, but we can represent these types of relationships by adding the document id of one document as a property value in another document.

In this chapter we’re going to add a new tasks collection to our API server’s database which will hold documents containing information about items on a user’s “to do” list.  Each document will have title, descriptioncompleted (boolean), and owner properties.  The title, description, and (optionally) completed properties will be sent from the user to the API server.  Since we’ll require an authorization token in the header of the request, we’ll use the user found by the auth middleware to set the owner property.

After we’ve created the model, we’ll add a new router that serves a new API endpoint to allow users to create tasks.  Later, we’ll continue this development and add endpoints that allow a user to retrieve all of their tasks, retrieve an individual task, modify a task, and delete a task. When done, we’ll have the following new endpoints.

POST /tasks       // Create a new task
GET /tasks        // Get a set of user's tasks
PATCH /tasks      // Modify a task
DELETE /tasks     // Delete a task

Let’s begin.

Create a Tasks Collection

Create a file named task.js in your src/models directory. There we’ll add a schema for the task document and generate a mongoose model based on that schema.

Much of the task schema is similar to the user schema. The objects defining the title, description, and completed properties contain type, required, trim, and default properties.  Two items in the schema that are new are the owner property and the timestamps option.

The owner property will hold the id of the user who created the task.  The type, therefore, is Schema.Types.ObjectId. The ref property in the specification is similar to a foreign key constraint.  It specifies the name of the model (User) to which the id belongs.

Notice that we now pass two objects to the Schema constructor.  The second object is a set of optionsThe timestamps (plural) option tells mongoose to automatically add createdAt and updatedAt properties to the documents and set them to Date objects when the document is created and modified, respectively.

Take a moment to copy the code below to you tasks.js file.

const mongoose = require('mongoose')
const User = require('./user')

const Schema = mongoose.Schema

const taskSchema = new Schema({
    title: {
        type: String,
        required: true,
        trim: true
    },
    desc: {
        type: String
    },
    completed: {
        type: Boolean,
        default: false
    },
    owner: {
        type: Schema.Types.ObjectId,
        required: true,
        ref: 'User'
    }
}, { timestamps: true })

const Task = mongoose.model('Task', taskSchema);

module.exports = Task

Add a toJSON Instance Method

Refer to your user schema and add an instance method named toJSON to the tasks schema so that when a task object is stringified it does not include the version property.

Create a Tasks Router

Create a file named tasks.js in your src/routers directory.

Our tasks router is similar to our users router.  The POST /tasks endpoint will be used to create task documents in the tasks collection.  Since we only want authorized users to create tasks, we’ll require the HTTP request to have an authorization token in the Authorize header and we’ll use the auth middleware to validate it.

The code for the body of the  POST /tasks endpoint handler is similar to the other endpoint handlers that we’ve created.  One difference in this handler is in the syntax we use to construct a task.

const user = req.user

const task = new Task({ 
    ...req.body, 
    owner: user._id 
})

In the above code you will first recognize that we obtain the user object that the auth middleware added to the req object.  Next, we create a new task using the Task constructor.  The Task constructor takes an object containing the data that we want to store in the document.  In the users router when simply passed req.body to the User constructor.  Notice here, however, we are constructing an object.  We do so because we want to pass an object to the Tasks constructor that contains the data passed by the client as well as an owner property whose value is obtained using the user object provided by the auth middleware.

The ellipsis in the expression ...req.body is the spread operator which evaluates to a comma separated list containing the object’s elements (key-value pairs).  This sets into the object that is passed to Task the properties (and values) that were passed by the client.  We follow this with the owner property which is set to the value of the _id property in the user object.

Copy the code below into your tasks.js file to create your tasks router.

const express = require('express')
const Task = require('../models/task')
const auth = require('../middleware/auth')

const router = new express.Router()

router.post('/tasks', auth, async(req, res) => {
    const user = req.user

    const task = new Task({
        ...req.body,
        owner: user._id
    })

    try {
        await task.save()
        res.status(201).send(task)
    } catch (e) {
        res.status(400).send(e)
    }
})

module.exports = router

Update App.js

Add the following two lines in the appropriate locations in your app.js file.  The lines will import that task router and instruct express to utilize it, respectively.

const taskRouter = require('./routers/tasks')
...
app.use(taskRouter)

Test With Postman

Start up your API server on localhost and launch Postman.  In Postman, create a new folder named /tasks under the Web App API collection.  In that folder, create a new request named Create Task and configure it to send the request to the POST {{url}}/tasks endpoint.  Add a raw JSON object to the body of the request that hold a title and description (desc) of a task.

Test the endpoint with Postman and double check your database with Compass to verify the data (including timestamps) is being stored in the database.  If the tasks are not being stored in the database or you are not getting from the Postman request a 201 response status along with the task object, debug your code.

Deploy To Heroku

Once satisfied, deploy your API server to Heroku and test.

 

© 2022 – 2024, Eric McGregor. All rights reserved. Unauthorized use, distribution and/or duplication of this material without express and written permission from the author is strictly prohibited.