Day 6
Using mongoDB in the nextjs app.
So far covered:
- getStaticProps, and getStaticPaths and getServerSideProps
- They allow us to fetch data for pre-rendering those pages, So that we pre-render the pages with the data, instead of without the data they might need.
Upto this point, we’re only working with dummy data though (not actually fetching from anywhere) - it is actually hard-coded data
We do have this Add New Meetup page here, which would allow users to enter data for a new meetup.
We will now add a real backend, with real DB, from which we then fetch it.
With this we will also see the last major NextJS feature API routes, which is added by Next to the React apps.
NextJS makes it easy for us to build an API (a backend API) together with our front-end React app in the same project, using API routes.
API routes are a special routes, (special pages - if you wanna call it like this) which don’t return HTML code, but which are instead about accepting incoming HTTP requests, also post, patch, put, delete requests, whatever you need - with JSON data attached to them and which then might do whatever you need to do. For example, store data in a database and then return JSON data.
So you could say API routes allow you to build your own API end points as part of this nextjs project.
And they will then be served by the same server as your next app.
Working with API routes:
Now to add API routes, you add a special folder in your pages
folder, and that’s a folder named api
.
Just as the
pages
folder has to be namedpages
, this folder has to be namedapi
and it has to be in the pages folder.
Then NextJS will pick up any JavaScript files stored in there and turn those files into API routes that can be targeted by requests and that should receive JSON and return JSON
- file names will act as path segments in the URL. For example, a
new-note.js
file again, - In those JavaScript files here, you then don’t create a React component function.
These API routes are not about defining, rendering or returning React components. Instead in there, we will define functions which contains server-side code because API routes will only run on the server, never on the client.
Decoding them will never be exposed to the client. So we can also use credentials in API routes without compromising them.
And those functions are then simply triggered whenever a request is sent to this route, i.e. /api/new-note
here. This would be the URL of this file - a request to this URL will trigger the function which we have to define in this file.
Now often these function are named handler
but the name is up to you, the important thing is that it’s exported.
MongoDB
Cluster Tier = M0
Network Access > add IP address
Database Access > create 1 user
Clusters > Connect > Connet your application
npm install mongodb
import {MongoClient} from mongodb
in /pages
- create a folder
api
- a js file in
/pages/api
- export a function (name it say
handler(req, res)
)
Make sure the folder is
api
and notapis
etc. Wasted 1.5 hrs details below.
if req.method == “POST”
call MongoClient.connect (.....)
- It returns a promise, so make the handler
async
, and do await MongoClient.connect(....)
pass the URL of your DB.
Write name of DB in URL - if doesnt exist it will be created on the fly.
const client = await MongoClient.connect.....
Collections ~are like tables Documents ~are like entries in those tables
1 note = 1 document
Overall a collection holds multiple notes (multiple documents)
const db = client.db()
const notesCollection = db.collection('notes')
meetsupCollection.insertOne({});
//insert your data here
const data = req.body;
//its async
await meetsupCollection.insertOne(data);
const result = await meetupsCollection.insertOne(data)
console.log(result);
//also possible: catch, error handlng in DB collection - not done here.
//send back a response, using res
object
201 - to indicate that something was inserted.
res.status(201).json({message: "Note inserted!"})
Sending request to API route from Frontend.
Do it in /pages/api/new-meetup/index.js
async function addMeetupHandler(data){
const response = await fetch ('/api/new-neetup');
}
but this is not complete yet, not indicated that this is POST req! no data sent!
const resonse = await fetch('/api/new-meetup', {
method: 'POST',
body: /////////////
}
);
what to pass in body - an object with data.
JSON.stringify
the data, add set this content type in header
Also add headers:
const response = await fetch('/api/new-meetup', {
method: 'POST',
body: JSON.stringify(enteredNoteData),
headers: {
'Content-Type': 'application/json'
}
}
);
const data = await response.json(); // just like we always do to responses from fetch function
console.log(data);
Check by logs - submit form and see “inserted!” log on browser console
Navigate away from the form - use useRouter hook
const router = useRouter(); // at start of react function component.
////
// when want to redirect after all calls, submit successful
// 2 options - replace and push.
// by doing replace, people cant go back with back button.
router.push('/')
Getting data from DB
next adds feature of using
fetch
on serverside fns as well (GSP etc)We could do it like, in page component GSP write a connection to our API route > that API route connects to mongoDB
page component GSP > nextjs api route > mongoDB
but its GSP and already serverside - directly call DB from GSP.
page component GSP > mongoDB
- When you import something here in a
page component
file and that something is then only used ingetServerSideProps
orgetStaticProps
, the imported package will not be part of the client side bundle.
import {MongoClient} from 'mongodb'
//nextjs will detect that if this package is only used in GSP, GSSP etc, it wont include them in the client bundle on build. Security win. Bundle size win.
in GSP:
const client = await MongoClient.connect(....)
const db = client.db()
const notesCollection = db.collection('notes');
// meetsupCollection.find(); //async returns a promise
const notes = await notesCollection.find().toArray();
//close connection to mongoDB before returning the data.
client.close();
return {
props:{
notes: notes
}
}
will Get this error just by using above.
looking at data on DB:
the object is not directly useable as we are trying.
Transform the array. Map the array, for e.g. like this:
return {
props:{
notes: notes.map(item => {
title: item.title,
address: item.address,
image: item.image,
id: item._id.toString() // this was causing problem , so needed to transform.
})
}
}
Remove all dummy now, it works with DB!
The data will be fetched from DB! When? whenever the page is regenerated, data will be fetched.
- on every visit? No its not GSSP
- on build? yes - fetch from DB on build. Because GSP is used. If revalidate is used, it would build on every few time interval, so fetch every few time interval that is set.
Some very silly mistakes on handson:
- Named the folder
pages/apis
by mistake. Next gave error as if trying to use MongoDB from client side - because it was not under/pages/api
. The error wasModule not found: Can't resolve 'dns'
Wasted 1.5 hrs.
A slightly different syntax used (not doing MongoClient.connnect(), but initializing new MongoClient… as per npm documentation of mongodb package. But above mongodb code of Maxmilian also works - tested)