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 named pages, this folder has to be named api 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

  1. create a folder api
  2. a js file in /pages/api
  3. export a function (name it say handler(req, res))

Make sure the folder is api and not apis 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


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 in getServerSideProps or getStaticProps, 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.

error

looking at data on DB:

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.

  1. on every visit? No its not GSSP
  2. 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:

  1. 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 was Module 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)