Now, let's evolve
this very simple first API a little bit.
For this, I'll first of all add a different folder structure
even though for this simple API,
we, of course, wouldn't need that.
But I wanna show that you still build it
as all the other websites you learned about
throughout this course
just with different data that's being sent back
with the response.
Hence I'll add a controllers folder
and a models folder.
And I'll add a data folder
with this database.js file,
which I will soon use for connecting to the database.
And I will also add a routes folder.
In that routes folder,
I'll add a quotes.routes.js file.
In the controllers folder,
I'll add a quotes.controller.js file.
And in the models folder,
I'll add a quote.model.js file.
Again, this is all overkill
but it makes for a great demo.
Now, let's start in the routes file.
There, as before, I, of course, wanna import express
by requiring express here like this.
And then get access to the router
by calling express.Router
as we did it many times before in this course.
And of course, we also wanna export this router in the end
but in between, we wanna configure it
and, for example, add our get route here.
This one get route for /quote
or if we load these routes such that /quote
is already set as a prefix and filter in app.js,
we can have just slash here,
and then point at our controller.
And then to show you what I mean with that prefix,
in app.js, we can, of course, include our routes.
So here I can add my quoteRoutes
by requiring ./routes/quote.routes like this.
And then instead of registering this route here like that,
we can use app.use
and then add /routes as a prefix here,
and as you learned, this will act as a filter.
And it will then be removed from the path thereafter
and then matching requests with that prefix removed
from there path will be forwarded
to the middleware we specify here.
In this case, to our quoteRoutes config
where the router will then check
which route best fits this incoming request.
And here, of course, we only have one route,
so if that doesn't fit, no route will handle this.
So that's this first step here.
Now we can add the quotes.controller
or add some functions to it to be precise.
For example, the getRandomQuote function,
which will be a middleware in the end,
as all controller actions are.
And hence here we get request, response and next as always.
And here we then can send back our data,
which I sent back before as well
with res.json and quote set to our quote.
We'll get a random quote here soon.
And then, of course, we wanna expose that
and here we just have one function,
so we could directly export it like this
but if we anticipate that we might have more functions,
more actions in the future,
we can instead export an object,
add a key of getRandomQuote
and point at the getRandomQuote function.
And with that, we can then go to quotes.routes
and include our controller here.
And I'm going through that with a bit more speed
because these are all things we did multiple times
throughout the course already.
There's nothing new about this.
I include my controller here
from controllers/quotes.controller like that.
And then here for this route,
we can point at quotesController.getRandomQuote.
Now, this would be our first step.
Now, I wanna store my quotes in a database
and then get random quotes from there
and therefore, I will install mongodb, this driver
so that I can send requests to the database.
Like this.
And I will also install as a development dependency
with save-dev nodemon
so that we can use that for running our Node server.
That's, of course, optional, as it always was.
But with that in package.json,
I'll quickly edit this script,
and add a start script
where I use nodemon to run app.js.
And then more importantly, in the database.js file,
I wanna set up a connection to my MongoDB database.
And for this, I have a MongoDB database server running
on my local machine.
As in many other course sections,
if you wanna follow along on your machine,
you need to make sure you have one installed
and up and running on your machine as well
just as shown before in the course in the MongoDB sections.
So in here we can then import mongodb like this.
And then add a function
for initializing the database connection so to say.
And I'll add a database helper variable here,
which will hold the connected database as soon as we got it.
And this will be a async function
because in here, I will use the MongoClient,
which we get from the MongoDB package like this.
MongoClient is mongodb.MongoClient like that.
And then in this async function,
we can await MongoClient.connect,
and then here we have our URL,
which by default is mongodb://localhost:27017.
And this will return us a connected client.
And then we can store the concrete database
to which we wanna talk by calling db on this client,
and to db we pass the name of the database,
and I'll just use first-api here.
The database name, of course, is up to you.
And then I'll add another function
with the name of getDb
where we could, first of all,
check if we maybe don't have a database.
So we tried to call this function too early
in which case, I'll throw a new error
where I say database not connected, for example.
And else if we make it pass this if check,
I return this database helper variable,
and then in the end,
we just have to export these two functions
that we defined here
with module.exports pointing at an object here
where we export initDb like this.
And where we export getDb like this.
So that's this.
And now with this added,
we can go to the model, to the quote.model here,
and in there, define a Quote class,
and already import db by requiring going up one level,
data/database, and in there,
I'll now add a static async method getRandomQuote,
which should do what the name suggests.
And, of course, I'll also already export this Quote class
so that we can use it in other files.
Now, for the logic in here,
what I'll do, and I'll say right away,
this is not the only way of doing it,
but it's hopefully a very understandable way,
what I'll do here is I'll get all my quotes
by awaiting db.getDb collection quotes.
That's where I expect the quotes to be stored.
And we'll have to add them manually
because as mentioned before,
this API won't have any endpoints or methods
for adding quotes.
We'll later build another example API,
which will have all CRUD operations.
This one won't.
So here I just reach out to quotes,
and I wanna find all quotes
by calling find and get them back as an array.
So with that, I'll fetch all the quotes in the database.
And, of course, this is a bit inefficient
if we only wanna get a single random quote,
but again, there would be alternatives with MongoDB
but this approach hopefully is very understandable,
and since the focus of this section here
is not optimizing MongoDB queries,
I would have a whole course about MongoDB
if you would be interested in that.
But instead here, I wanna focus on building APIs
and therefore, I wanna focus on writing understandable code.
So here we get all the quotes.
And now to get a randomQuote,
we can use a object built into JavaScript,
both on the browser side,
as well as on NodeJS.
And that's the Math object.
There we can use the floor method
to round a number down.
So 0.5 would become 0.
1.2 would become 1.
1.8 would also become 1.
That's the idea here.
And the value which I wanna round down
is the result of another calculation
where I use Math.random,
which produces a random number
between zero and one, one excluded.
So it's actually the range zero up to 0.99999.
And I'll multiply this
with quotes.length.
So the number of quotes I have.
Now, to give you an example,
if we had an array with three items,
and yes, these are not quotes
but these are items,
then the length is three.
Keep in mind that the indexes of the items
are zero, one, and two though.
With the formula I have up here,
Math.random could be any value
between zero and one, one excluded.
So it will never be one.
So if Math.random is 0.8, for example,
that would be multiplied with three
and this, of course, would give us 2.4.
Thanks to Math.floor,
2.4 would become 2
and therefore, we would get the item with index two,
which is the last item here.
If this would not be 0.8 but 0.1,
this would, for example, become 0.3 as a result,
and Math.floor of 0.3 would be 0,
so we would select the first item in the array.
And that's what I'm doing here with the quotes array.
And then I'll return that randomQuote which I got here.
Now back in the quotes.controller,
we can, of course, include this Quote class
by requiring it from models/quote.model,
and instead of returning a hard coded quote here,
I got my randomQuote by using await,
and for this, we wanna turn this into async function
as we did many times before in the course.
Quote.getRandomQuote.
And then it's this randomQuote,
which I'll return as a quote here
in my response, in my JSON response.
And still that is basically the only thing
that makes this demo project different compared
to all the other projects we had in this course.
We're not rendering a template.
We have no HTML code and no templates therefore,
and we have no CSS code therefore
but when it comes to the backend logic,
to models and controllers and so on,
it's the same as always.
Nothing is different there.
And that's why it was so important
to use the other approach first
because now you can easily also build APIs
but you can also build regular websites.
Both is possible.
Back to this example though,
with that done, we can go back to app.js
because, of course,
there we have to initialize our database.
So here I'll get my db object by requiring ./data/database.
And then before we start listening on port 3000,
I'll call initDb.
This will return a promise
and since I'm not inside of a function here,
I can't use await here.
In some NodeJS versions, you actually can
but it's safer to just use the other way
of handling promises,
which is to use the then method.
Then pass an anonymous function to it.
And listen inside of this anonymous function,
which will only be executed once this promise here is done.
So once the database connection was established.
And, of course, we can then also add catch here
to catch errors with this anonymous function
and, for example,
console.log connecting to the database failed like this.
Now, we can also, by the way,
add our default error handler,
which we also did before in the course
with this error handling middleware,
which could also be stored
in a separate file in a middlewares folder
if you wanted to.
And then there we could also add error handling.
The difference again would be
that we don't render some 500 template now,
but that instead we would still return some JSON data.
Maybe with an error message,
like something went wrong
or maybe more details if we have them.
We still also wanna set our status code
and with APIs, that status code
is actually even more important
that it was with regular websites.
There it's also recommended to set it
but with APIs, since we're not returning anything visual,
it's even more important to set clear status codes
and return clear data
so that the user, the customer of an API
will be able to understand if a certain action succeeded,
and if it didn't, why it didn't.
So here it's even more important
to set a status code of 500
to indicate that something went wrong on the server side.
And here I need a semicolon.
Okay, so that's the updated app.js file,
updated model and controller and routes.
This should all be connected.
Now, of course, we need quotes
so that fetching a randomQuote actually
has a chance of succeeding.
For this, I opened Mongo shell
and again, I have the MongoDB server up and running.
And I then use this first-api database
because that is the database I'm connecting to here
in my database.js file, first-api.
And now once I'm connected to this database,
I wanna insertOne quote into quotes.
Or actually not just one but I'll use insertMany,
which simply allows me to insert many documents.
You simply pass an array with square brackets
to insertMany, and then your documents
as comma-separated objects so to say.
And then for every quote,
I just wanna have a text field let's say,
which then holds the quote text.
And here I'll just add some dummy quotes
all stored in a text field in these quote documents.
Here we go.
And now I'll hit Enter
and these three documents were now inserted,
and hence if I access my quotes
and find all the documents with the proper syntax,
here they are.
Now with that all done,
back in quote.model, we have one last adjustment to make.
I'm, of course, still returning randomQuote,
which is a number though,
not the actual quote,
so this is actually the randomQuoteIndex
if we wanna label this correctly,
which I want, so I renamed it.
And instead I get the concrete randomQuote now
by accessing quotes
and then the quote for this randomQuoteIndex.
And it's then this randomQuote,
which I wanna return
but also not the quote like this,
but instead, we should access the text property here
because I stored a concrete quote text
under that text property
in those documents in the collection.
Hence we should access the text property here.
In addition, I noticed that in app.js,
I had a tiny mistake.
The prefix for the routes here
should, of course, not be /routes but /quote.
Thinking and talking simultaneously
did not go well together here.
So this should be /quote.
With all that done though,
we should be able to run npm start
thanks to this newly added script.
We now get different quotes here every time we reload.
And it's a random quote
so you can't tell which quote you'll get.
And this is now an API that does a bit more
than it did before but,
and that's really important,
in the end, it's really nothing new.
It is just a controller, a model
and some routes, and the only key difference
is that we never turn views
because we got no views and we want no views.
And we have no front end CSS or JavaScript code.
Instead, all we have here is backend code
and we exchange data.
We return JSON data.
Now, to finish this, of course,
we could also add error handling here therefore,
just as we did it before in the course
because all these concepts you learned about didn't change.
So when you work with a database,
you might want to try catch this,
and then call return next error here
to forward this error to Express.js error handler.
And since randomQuote is now scoped
to the try block but I wanna use it thereafter,
we now either have to move this code here
into the try block so that it has the same scope
or we keep it here
and we then add this as a variable, the randomQuote,
and we set this variable inside of the try block.
Both solutions will do the trick.
Now, this won't change the behavior here
but again, it is important to understand
that you build a regular web backend.
The key difference
is that we don't have any front end code though
but that instead we exchange data.