Contents
Have you ever think about connect to Mysql database or Mongodb database with NextJs?
Or have you asked some questions as:
- How to connect to a database with NextJs?
- How not to create a separate api server for NextJs?
- How to require
server file
inside a NextJs page?
Well, you may know that Next.js is a great framework for creating server side rendering application. I just try Next.js for a few months and was very excited about it’s design and easy to use.
Check out these tutorials about Next.js
Next.js + Ant Design with less – Advanced Nextjs and Ant design scaffolding
Convert HTML template into NextJs app
NextJs + KoaJs Create custom NextJs server with KoaJs
[Tutorial] Create react based server side rendering app with NextJS
NextJs Data fetching – Combine Server Side and Client Side fetching
Cloudreports – Next.js tutorials
Next.js was fully supported server side rendering. For now, it may be the best choice for building react based SSR website.
Follow this post NextJs Data fetching, we have got a method for fetching data with Next.js through getInitialProps
. But this method is still have disadvantages: You have to build an external api system for NextJs app to work!
Why external api system is a problem here?
Firstly, with external api, you have to build a separate app that work as data provider. Manage two separate Nodejs apps for a simple web is complex.
Secondly, when you have more than one app, you also have to manage more complex deploy, maintain, upgrade process.
And last, when in development, it is not convenience to work on two separated projects while you “should” work on single one.
Why NextJs support SSR, it work as a real SSR system and you still have to build api? Like a CSR SPA???
In this post, we will follow an idea that help answer some questions like these:
- Can i merge external api service and NextJs app in to one?
- Can i use database or connect to a database in NextJs app?
- If i have important information and want to use it in
getInitialProps
, how can i secure these information?
Because NextJs will expose getInitialProps
for both client side and server side, so if you put any code here, you it will be exposed to both side code. It will be bad practice if you put any secure information here. This is also the reason why NextJs official document only saying about fetching data from external api.
Any code inside getInitialProps
must be compatible with server side and client side code, any script that only work on server side will cause error during NextJs build process. This is the reason why you cannot do Database connect inside getInitialProps
, you shouldn’t put any server side logic here.
You may give up here but slow down, there is something else that help 😀
Before go deep into the solution, i will describe something about NextJs build process.
NextJs won’t build whole server side and client side code at once, because the runtime and environment for these sides are difference. So NextJs will use difference config, difference process for building each side code.
Beside that, NextJs also expose some special variables during the build process. We can use these variables for optimize our source code. Bellow are some use case of exposed NextJs variable:
- Using as environment variables: these variables will be use while build process (not runtime)
- Use for conditional building: you may change some setting based on build environment (development, production)
- Use for minified source code without losing readability
Take a look at Use for conditional building
can you guest what is it mean?
One of the build variables
you should noted is: process.browser
. In client side building, it will be passed as true
and in opposite undefined
for server side building.
Let me explain through this example:
Create file pages/conditionalBuild.jsx
import React from 'react'
export default class ConditionalBuildingPage extends React.Component {
static async getInitialProps() {
let resultText = '';
if (process.browser) {
resultText = 'This is text for CLIENT'
} else {
resultText = 'This is text for SERVER'
}
return {text: resultText}
}
render() {
return <div>{this.props.text}</div>
}
}
run npm run dev
then navigate your browser to http://127.0.0.1:3000/conditionalBuild
When NextJs running build, it will create two directory inside .next
server
for storage server side codestatic
for storage client side code
Look at these files:
.next/static/development/pages/conditionalBuild.js
.next/server/static/development/pages/conditionalBuild.js
in both two file, find this text: key: "getInitialProps"
and look into followed script
This is the result:
File: .next/static/development/pages/conditionalBuild.js
File: .next/server/static/development/pages/conditionalBuild.js
Surprised!!! Instead of expose full code in original getInitialProps
, NextJs (with help of webpack/babel) was by pass the check condition. While building, NextJs can check the build variables and decided which code to be expose; it will remove unnecessary code on each side and only keep the usable part. So, the point here is that: Client side code won’t consists Server side code. Server side code won’t be mixed with Client side code too. So awesome!!!
Now, do you think about what you can do next? Did you got the answers for three questions at the starting :D? If not, keep calm and i will give you the answers :v
The idea is quite simple! Instead of calling api on both client side and server side, we will ony do it on client side code. The api used for client fetching will be a “local api”. These apis are served through our NextJs Custom Server.
On server side, we will call direct service inside server source code!
Let’s start on doing this awesome thing!
Imagining we have to create a component that display a list of some countries.
First of all, clone this repository and switch to branch custom-server-koajs
Prepare some resources:
Create libs dir:
mkdir libs
Create file libs/dataProvider.js
with this content
exports.countryList = () => {
return [
{"name": "Australia", "code": "AU"},
{"name": "Austria", "code": "AT"},
{"name": "Azerbaijan", "code": "AZ"},
{"name": "Bahamas", "code": "BS"},
{"name": "Bahrain", "code": "BH"},
{"name": "Bangladesh", "code": "BD"},
{"name": "Barbados", "code": "BB"},
{"name": "Belarus", "code": "BY"},
{"name": "Belgium", "code": "BE"},
{"name": "Belize", "code": "BZ"},
{"name": "Benin", "code": "BJ"},
{"name": "Bermuda", "code": "BM"},
{"name": "Bhutan", "code": "BT"},
{"name": "Bolivia", "code": "BO"},
{"name": "Bosnia and Herzegovina", "code": "BA"},
{"name": "Botswana", "code": "BW"},
{"name": "Bouvet Island", "code": "BV"},
{"name": "Brazil", "code": "BR"},
{"name": "British Indian Ocean Territory", "code": "IO"},
{"name": "Brunei Darussalam", "code": "BN"},
{"name": "Bulgaria", "code": "BG"},
{"name": "Burkina Faso", "code": "BF"},
{"name": "Burundi", "code": "BI"},
{"name": "Cambodia", "code": "KH"},
{"name": "Cameroon", "code": "CM"},
{"name": "Canada", "code": "CA"},
{"name": "Cape Verde", "code": "CV"}
];
}
Modify server.js
file:
- Add this line after
const handler = nextApp.getRequestHandler();
- Add these lines before
router.get('*', async ctx => {
(this is important)
global.SERVER_APP_ROOT = __dirname;
const dataProvider = require(SERVER_APP_ROOT + '/libs/dataProvider');
router.get('/api/country', async ctx => {
ctx.body = dataProvider.countryList();
});
Start NextJs server and check if api http://127.0.0.1:3000/api/country
worked.
We will continue by working with ConditionalBuildingPage
component inside pages/conditionalBuild.jsx
Look at this script (we will call this: S1), it will describe the idea of self-api-server
let conditionalDataProvider = null;
if (process.browser) {
conditionalDataProvider = async () => {
let res = await fetch('http://127.0.0.1:3000/api/country');
let countries = await res.json();
return countries;
}
} else {
conditionalDataProvider = async () => {
const dataProvider = require(SERVER_APP_ROOT + '/libs/dataProvider.js');
let countries = dataProvider.countryList();
return countries;
}
}
As you can see, in above script we check if the current built is for client or server. If this built is for client, we will call api that we defined before. If it is for server, we will directly get data from dataProvider
Modify pages/conditionalBuild.jsx
and change getInitialProps
to
static async getInitialProps() {
let countries = await conditionalDataProvider();
return {countries}
}
Add function S1
after line import React from 'react'
Change function render
to
render() {
return (
<div>
<h1>Country list</h1>
<ul>
{this.props.countries.map((country, i) => {
return (
<li key={'country-' + i}>{country.name}</li>
)
})}
</ul>
</div>
)
}
Restart NextJs server and check http://127.0.0.1:3000/conditionalBuild
wow, we got an error: Unhandled Rejection (Error): Cannot find module
The reason is that, when webpack compile source, it will convert require
to __webpack_require__
and __webpack_require__
does not understand dataProvider.js
file path. We can config web pack to include libs/*
into it built. But there are some reasons prevent us do this:
Our idea is that we will put some logic code that work only on server side inside
libs
directory. These code may be do database connecting, hash or any thing else that should be secure. We don’t need to build these code by webpack, it should be run as a normal javascript file.
There are many library that writen in es5, they just userequire
andexports
. When mixing with component file (jsx
files), we have to config so many complicated thing. If not, webpack will throw error on build process, or if it success on build process, then the built file should not work as expected
Re-building all the files that does not required will only cause the build process take more time, and it is not a good practice too.
Now it is time for our tricky require:
Modify pages/conditionalBuild.jsx
and change
const dataProvider = require(SERVER_APP_ROOT + '/libs/dataProvider.js');
to
const dataProvider = eval("require('" + SERVER_APP_ROOT + "/libs/dataProvider')");
Restart your NextJs server and reload 127.0.0.1:3000/conditionalBuild
now it work as expected!
Let me explain about the reason for using eval
in this case
When using eval
, require
command is passed as string. Webpack will not analyze any thing inside this string, this require
part is only existed in webpack build as a normal parameter!
Conclusion
This tutorial present a simple idea for design a better NextJs app (IMO), follow this tutorial, you may have a generality look about this idea.
Look at file libs/dataProvider.js
, this file is just a normal js file. But the way it was called inside server.js
and conditionalBuild.jsx
is the key here
Inside libs/dataProvider.js
you can do almost thing as a normal NodeJs file, these can be:
- Connect to database (it is what called “Connect database inside NextJs app`)
- Hashing/Encrypting/Decrypting…
- Storage server side secure logic
Well, i hope you understand my idea and can apply this on your project! If i have any mistakes or if you have any problem while following this tutorial, please let me know.
Thank you!
Git repo for this turotial:
https://github.com/cloudreports/my-nextjs/tree/custom-self-api-server
Discussion about this post