Now, this was our first API,
our first basic API that we built.
Now, we will move on to a different example,
a different API which we'll build,
which will be a little bit more complex
and where we will see more than just GET routes in action.
Here, we are going to build
a Simple Todo Management Web API,
and therefore in the end Web App
because I will later also provide you
with a decoupled frontend,
but we're going to start with the API.
And the goal of this API which we build
is that we allow users of this API to manage todos.
For this, we'll have a GET route to /todos
which should retrieve and return a list
of all the todos that are stored in the database.
And for this API here,
we can say that a todo is simply a document, an object,
which contains the todo text and a unique id.
We'll have a POST /todos route, an endpoint therefore,
which will accept incoming data,
so where the request should contain some data
in the request body in the JSON format,
which we will parse
and then store as a new todo in the database.
And, of course, you wanna return a success response,
maybe with that newly created todo in the response body.
And then I got two more routes,
for example, a PATCH route where we target a specific todo
with /todos and then slash the id of a specific todo
to update the text of an existing todo,
and we'll have a DELETE /todos/ some specific id route
which allows us to delete a todo,
so that we have all these CRUD operations at this example
of this basic todos app here
or of this basic todos API here.
Now, that's the next API we'll build, and as I mentioned,
later I will also provide you with a fitting frontend,
some JavaScript code, browser-side JavaScript code,
that will talk to this API,
but that will be the next step.
First of all, we're going to build it.
Now, to build this API,
I got a brand new project here which you also find attached
where I got an empty models and controllers
and routes folder.
You'll have to create those folders on your own.
In the data folder,
I got the database.js file where we connect to a database
now to the second-api database on the database server,
which you therefore, of course, need to start.
You need to have MongoDB installed and started.
And then app.js has just got some basic setup.
I got no routes yet because we'll add those together,
and I also got no model and nothing like this.
We're all going to add this together.
Now, if you download this snapshot,
you will have to run npm install
so that you get this node_modules folder.
And then you could run npm start,
but, first of all, I'm going to write some code here
and add some logic to this web API before I'll start it.
And I'll start by adding a model, the todo.model.js file,
where I wanna add a Todo class
which helps us store and create todos.
For this here, I will add a constructor
which is this method which is called automatically
when a new instance of this class is created.
And here I expect to get the todo text
so that in the constructor,
I can add it as a text property to the to-be created object.
And, of course, we can already also export this Todo class
so that later we can use it outside of this file.
Now, eventually, I want to be able to save my todos
which exist as objects in my running web application here
to the database.
And for this, I'll add a save method
as we did it before in the course for other examples
like the online shop as well.
And in this save method,
I, of course, wanna reach out to the database
so I will actually require my database file
from the data folder here
and import this db object
so that here I can then run get.Db,
reach out to a collection, let's say the todos collection,
and then there, I want to insert one new todo.
Now, for this todo which I wanna insert,
I only wanna add a text property
to this document that should be inserted,
and I'll set this equal to this.text.
Now, this will return a promise,
and hence we could turn this into a async function
and await this,
but here I'll just return this so that I return this promise
which is returned by this line of code
as part of this save method.
Of course, this is actually only the correct code
if we're creating a new todo,
so I also want to have code that updates an existing todo,
just as we also had it before in other parts of this course.
For this in a constructor,
I will actually accept an id as a second parameter,
though this might not always be provided.
If we create a todo for the first time, we won't have an id,
hence no value for this parameter will be provided,
and therefore id will be undefined,
but I will still store it, and it might be defined.
And because it might be defined
but isn't guaranteed to be defined,
here in the save method, I'll check if this id is truthy,
which means it does exist,
which means I wanna update an existing todo
because I have an id already and I'm calling save
so I must be updating an existing todo.
Else, if we have no id and we call save,
we are creating a new todo,
and that's therefore when I wanna execute this line of code.
Here in this first branch of the if block,
I will instead return db.getDb,
reaching out to the todos collection as always,
and then here we call updateOne to update a todo.
Now, for this, I need the id of the todo,
and I'll transform this to a mongodb.ObjectId
so that we'll never run into problems there,
and hence I'll require mongodb here in this file.
And then in the save method,
we can get our todoId as an object id
by using new mongodb.ObjectId
and passing this id,
which we know exists in this part here, in this if branch,
to object id.
So then I get the todo as an object id,
and hence here for updateOne for the first-parameter value,
I described my filtering condition,
and I'm looking for a document where _id in the database,
because that's the default id field MongoDB will add
to every document that's inserted in the database,
so where _id is equal to todoId.
And then with the second-parameter value here for updateOne,
we describe the update,
and here I'll just add this special $set key
where we then pass an object that describes
which keys or which fields in the document
should be updated with which values.
And here I wanna update the text field
in the document that was identified with this condition
with the text that's stored in this text.
So that's then my updating logic here.
With that, I got this save method.
Of course, we will also need a method for deleting a todo,
and therefore I'll add a delete method here at the bottom,
though you know the position doesn't matter.
And here we could check if we maybe don't have an id,
which means we try to delete a todo
which doesn't exist in the database yet
in which case we could throw a new error,
though that's totally optional,
where I say Trying to delete todo without id!
It is a case that actually shouldn't occur in our API
since we will write code that ensures
that we only call delete on todos that we do have,
but nonetheless this is extra check you could add.
More importantly, though, after this check,
we wanna actually delete the todo,
and therefore what I'll do here
is I will return db.getDb,
reaching out to the todos collection as always,
and here we can call deleteOne to delete one document,
and we need to provide our filtering condition here.
And now just as before,
for this, I wanna create a todoId as an object id,
so I will copy this line of code and add that here.
And then I wanna delete the todo
where _id is equal to this.todoId, like that.
So that's the delete method which should do the trick.
Now, we also wanna add static methods for fetching todos
or one static method for fetching all the todos.
For this, I'll add a static method getTodos
or getAllTodos, however you wanna name it.
And here I return db.getDb collection todos,
and we simply find all todos here,
but, actually, I wanna do a bit more than that.
I also wanna get them as an array,
as we did it before in the course as well,
but I also wanna transform all these todo documents
which we get from the collection
into objects that are instantiated like this,
so that are instantiated with help of this class.
Therefore, I will actually turn this into a async method
so that I can get my todoDocuments here
by awaiting this operation,
and then in the next step, I can transform them all.
So here we can then return todoDocuments and call map.
You learned that the map method is a built-in method
that exists on JavaScript arrays
which allows us to transform all the elements in an array.
And for this, we get an anonymous function here
which gets the todoDocument
because this function will automatically execute
for all the todoDocuments.
And then here, in this anonymous function,
I return my transformed object.
And for this, I'll instantiate my class here, my Todo class,
with new Todo,
and I pass in these two constructor values, text and id,
which is todoDocument._id
and todoDocument.text,
like this.
So this will now return these transformed todos,
and that's my getAllTodos method which I need here.
With that done, the model should be finished.
We'll see if we need to fix an error later.
It looks good though.
Now, we can add a controller,
the todos.controller.js file here,
where I wanna add a function for getting all the todos
and then also a function for adding a todo,
so I'll add all these actions right now,
and we're then going to populate them with more life later,
then a function for updating a todo,
and then also a function for deleting a todo,
so for these CRUD operations.
And, of course, all these functions should be exposed,
so I will export an object here
where I will add all these functions as keys
and then point at all these functions
so that they are available outside of this file here as well
with addTodo, updateTodo, deleteTodo, and so on.
And then, of course, all these functions
will get request, response, and next
because they are controller actions
and therefore middleware functions.
And we will need that todo.model which we just worked on
so we can require that from /models/todo.model.
And then with that,
then here, for example, for getting all the todos,
what we can do is we can turn this into a async function
so that we can get our todos by awaiting Todo.getAllTodos.
And we might wanna put this into a try-catch block
because, of course, since we're interacting
with the database here,
this could be failing,
so I wanna catch errors
and return next error here if we have an error.
Otherwise, thereafter, I wanna use my todos.
So, as always, I'll actually turn this into a variable
so that we have the right scope.
And then here,
I can use the res object to call the json method
to send back a response in the JSON format.
And the data which I wanna send back here
actually is my todos array,
but I'll actually send back an object with a todos key
which then holds this todos array.
We could have also send back
the todos array itself like this.
This would also work
because just an array is also valid JSON.
But here I'll wrap it into an extra object
and send back the array under a todos key in that object.
And that should fetch us all the todos.
Now, for adding a todo here,
that's the next action I wanna work on,
I, of course, need to get my todoText
which I wanna get from req.body.text like this,
assuming that the incoming request body will have
a text field.
So we'll have to make sure
that when we send a request to our API later
that this POST request has a request body
with a text field inside of it.
And then with that,
we can, of course, create our todo with new Todo
and pass the todoText to it.
The second-parameter value,
the id which we could also pass in,
is not passed in
because here we are creating a todo for the first time,
and therefore, of course, we have no id.
But with that, we got this todo,
and now we can call todo.save here.
This, of course, returns a promise,
and therefore, as always,
I'll turn this into async function and await this,
and as always, wrap this into try-catch
to catch any potential errors we might be facing
so that we can actually let
Express.js' default error-handler handle this
if something goes wrong, like this.
Now, if nothing goes wrong,
if we make it past this try-catch block,
then we did create a todo,
and then I'll send back a JSON response
because we always send back JSON responses
when we build such an API.
We never send back HTML, and we never redirect
because we don't care about what the client does.
We just exchange data, as you learned.
And here in this object, I'll just add a message of saying
Added todo successfully, or anything like this.
And I'll add a second key-value pair to this response data,
and that's the createdTodo
where I pass back that todo here which was created.
Or, actually, I'll not pass it back like this.
Instead, on that todo, I wanna set the id field
and set this equal to the id of the todo that was inserted.
And for this, it is very convenient
that in the todo.model in the save method,
we return the promise that is yielded by insertOne,
and it turns out that the promise yielded by insertOne
eventually results to an object
which holds the id that was inserted.
So therefore, here in todos.controller,
I get the result here
of that operation,
and then I can add a variable, insertedId for example,
which I can get from that result.
So insertedId is then equal to result.insertedId.
This field will exist on this result object
because this result object is generated by MongoDB,
by this MongoDB package,
and it will hold the id of the todo that was created.
And then I'll set todo.id equal to insertedId.toString
to force a conversion to a regular string
instead of having this object id.
And therefore now, this todo which I created here,
which doesn't have an id initially,
will now have an id
because I set that id after I inserted it into the database,
and therefore once I have that automatically created id,
and therefore now it's this updated todo
which now contains an id which I will return here.
Now, let's ignore update and delete for now.
These are two important controller actions already
which allow us to create new todos and to get todos,
and therefore now we wanna add routes for those actions
and then also add one important middleware to app.js.