DEV Community

aurel kurtula
aurel kurtula

Posted on

Introduction to Next.js - adding express and mongo to the project

Welcome to part three of my exploration of next. In part one I covered the basics: How to structure and style a basic site. In part two I covered how we can fetch data from an API and inject it within the react component lifecycle.

Today I want to add express and mongo to the project. That means in the same project next.js allows us to run express and react in a pretty easy way.

Lets get started

As you're about to see, most of the code that we'll write is pure express stuff. I have covered how to build a restful API with react and mongodb already, all that is going to be the exact same thing.

Let's install the required packages for this part of the tutorial

npm i -S express mongoose body-parser
Enter fullscreen mode Exit fullscreen mode

Now lets start by creating ./server/index.js page and add the following code

const express = require('express')
const next = require('next')
const bodyParser = require('body-parser')
const PORT = process.env.PORT || 3000
const dev = process.env.NODE_DEV !== 'production' //true false
const nextApp = next({ dev })
const handle = nextApp.getRequestHandler() //part of next config
const mongoose = require('mongoose')

const db = mongoose.connect('mongodb://localhost:27017/Photos')

nextApp.prepare().then(() => {
    // express code here
    const app = express()
})
Enter fullscreen mode Exit fullscreen mode

That's how easy it is to add express to our project.

There are only four lines of code that are next.js related and everything else is the usual express code:

  1. Require next.
  2. Initiate it by letting it know which environment we're using (I chose to call that nextApp and call the express instance app - because I can come back next few months and read the express code with no problem.)
  3. Next requires getRequestHandler()
  4. Then we run prepare on the nextApp, which I guess prepares everything (!) and then we run express.

Lets add the rest of the code that makes up ./server/index.js

nextApp.prepare().then(() => {
    // express code here
    const app = express()
    app.use(bodyParser.json());
    app.use(bodyParser.urlencoded({ extended: true }));
    app.use('/api/photos', require('./routes/index')) 
    app.get('*', (req,res) => {
        return handle(req,res) // for all the react stuff
    })
    app.listen(PORT, err => {
        if (err) throw err;
        console.log(`ready at http://localhost:${PORT}`)
    })
})
Enter fullscreen mode Exit fullscreen mode

Again, nothing new there, you can learn how to build an express site here. However, the only important thing is the start in app.get('*', ...). That just says all routers that aren't specified already, let next.js deal with them - which of course, in turn, it lets us deal with them in our react code.

Let's create the API routes at ./server/routes/index.js

const express = require('express')
const router = express.Router()
const Photos = require('../models/photoModel')

router.get('/', (req, res) => {
    Photos.find({}, (err, photos) => {
        res.json(photos)
    })
})
router.use('/:id', (req, res, next) => {
    console.log(req.params.id)
    Photos.findById(req.params.id, (err, photo) => {
        if(err)
            res.status(500).send(err)
        else 
            req.photo = photo 
            next()
    })
})
router
    .get('/:id', (req, res) => {
        return res.json( req.photo )
    })
    .put('/:id', (req, res) =>{
        Object.keys(req.body).map(key=>{
            req.photo[key] = req.body[key]
        })
        req.photo.save()
        res.json(req.photo)
    })
module.exports = router;
Enter fullscreen mode Exit fullscreen mode

Above we basically created the API points that we know our react code requires. When /api/photos is requested, we return all photos found in the database. If any type of request is made on /api/photos/:id then we have to find the relevant object in the database. Then depending on whether the request is a get or a put we either respond with the object or edit the object depending on the required data we've received.

Finally we need to create ./server/models/photoModel.js

const mongoose = require('mongoose')
const schema = mongoose.Schema

const photoModel = new schema({
    tagline: { type: String} ,
    image: { type: String},
    likes: { type: String},
    comments: { type: Array, default: [] }
})

module.exports = mongoose.model('photos', photoModel)
Enter fullscreen mode Exit fullscreen mode

And that's about it!

Running the app

Unlike in previous tutorials, now we want to run our entire application from ./server/index.js

node server/index.js
Enter fullscreen mode Exit fullscreen mode

Of course if you are following along you need to do two more things:

In the react code, /pages/index.js and /pages/photo.js we need to edit the addresses to where we are fetching from, now we need to point to 3000/api/photos. Also when fetching, we would change id to _id. The database we are using has to contain the data of course. You can import the data I've left in the ripository by running the following command:

mongoimport --jsonArray --db Photos --collection photos --file '/your-project-folder/data/db.json'
Enter fullscreen mode Exit fullscreen mode

That imports the data into Photos database, and inside a collection named photos.

The project now should be run by using express as the starting point. Hence you'd run

node server/index.js
Enter fullscreen mode Exit fullscreen mode

And due to the way we've configured the code, next takes over and runs the react code as well. So what we have is, anything in /api is handled by express and everything else is handled by react.

Whilst developing your application it be more useful to run nodemon server/index.js instead. nodemon monitors your code and restarts the server when ever you change the code. Very useful. However, you don't want it to restart the server when you modify the react code. Amongst other things it might get caught in a loop. Where next reloads react, then nodemon reloads the server due to file changes. To solve this you should create a folder called nodemon.json and add the following:

{
    "verbose": true,
    "ignore": ["node_modules", ".next"],
    "watch": ["server/**/*"],
    "ext": "js json"
}
Enter fullscreen mode Exit fullscreen mode

We're basically telling nodemon to concern itself with files that are within the server directory and that are javascript and JSON, and especially ignore the specified folders.

nodemon is a really useful package that you should install as a global package (npm install -g nodemon). nodemon basically monitors your node code and re-runs the server whenever files change.

The end.

That's it for this tutorial, hope you liked it. There are few more things that I want to cover in this series, so stay tuned and thanks for reading! The repository for this part of the code can be found in the branch labeled part3

Part One:

Part Two:

Top comments (5)

Collapse
 
pavindulakshan profile image
Pavindu Lakshan

When handle is used, I can no longer use GET requests(router.get()) in my express app.If handle function is removed,GET requests can be implements through POSTman,but then fetching data from the pages is stuck.How can I use get requests in express app with Next.js?

Collapse
 
thujone profile image
Rich Goldman

Why did you skip over this part:

In the react code, /pages/index.js and /pages/photo.js we need to edit the addresses to where we are fetching from, now we need to point to 3000/api/photos. Also when fetching, we would change id to _id. The database we are using has to contain the data of course

I'm not sure what you mean here, and I cannot continue with the tutorial.

Collapse
 
itskarelleh profile image
Karelle Hofler

THANK YOU SO MUCH for creating this three part series. It was very helpful.

Collapse
 
helloitsm3 profile image
Sean

It's a great tutorial and it works on local development. However, uploading to Zeit Now doesn't seem to work because it doesn't support custom servers

Collapse
 
xcyborg profile image
Jonney Shih

That's too complicated for me most of this code makes no sense