Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
June 20, 2021 09:41 pm GMT

Express NextJS - sample/tutorial integration

Context

While NextJS is a powerful tool in its own right, adding Express to it makes for a really powerful combo.

You may want to add a custom API to your application without spinning up a separate API service, or you may want to take control of routing, or run some middleware and fetch standard data for your pages before they are served. Perhaps you have an existing API server and you wish to enable it to serve some front end.

This type of set up is documented in NextJS itself: https://nextjs.org/docs/advanced-features/custom-server

In the standard example, they use Node's http package; we'll use Express to take advantage of its middleware and routing capabilities.

The integration

I've provided an example barebones integration at https://github.com/alexey-dc/nextjs_express_template

Let's take a look at some highlights.

The main entry point is index.js, which sets up the environment and delegates spinning up the server:

require("dotenv").config()const Server = require("./app/server")const begin = async () => {  await new Server(process.env.EXPRESS_PORT).start()  console.log(`Server running in --- ${process.env.NODE_ENV} --- on port ${process.env.EXPRESS_PORT}`)}begin()

Note that I'm relying on dotenv to load environment variables - e.g. EXPRESS_PORT, NODE_ENV, and a few others. You can see the full list of necessary environment in the README in the github repository.

In the server, both nextjs and express are initialzed, along with express midleware and a custom NextjsExpressRouter I built to take the routing over from NextJS into our own hands:

  this.express = express()  this.next = next({ dev: process.env.NODE_ENV !== 'production' })  this.middleware = new Middleware(this.express)  this.router = new NextjsExpressRouter(this.express, this.next)

The middleware I included is quite barebones, but serves as an example of what you might have in a real application:

  this.express.use(bodyParser.json());  this.express.use(bodyParser.urlencoded({ extended: false }));  this.express.use(favicon(path.join(__dirname, '..', 'public', 'favicon.png')));

The NextjsExpressRouter is really the heart of the integration. Let's take a closer look.

NextjsExpressRouter

The idea is to allow GET routes for pages to co-exist with API HTTP routes:

class NextjsExpressRouter {  constructor(express, next) {    this.express = express    this.next = next  }  async init() {    this.initApi()    this.initPages()    this.initErrors()  }  initApi() {    return (new (require("./routes/api.js"))(this.express)).init()  }  initPages() {    return (new (require("./routes/pages.js"))(this.express, this.next)).init()  }// .../* Some standard error handling is also included in the repo code */}

I split out the API from the page routes into separate files, and I find that as the codebase grows, it helps to impose some sort of grouping or hierarchy on endpoints. Pages and API calls seem like the most basic organization. Note I made the init() function async. In this case we don't need to run any I/O operations or other async initialization, but in the general case we might want to.

For my larger projects, the API typically itself has several sub-groups, and sometimes pages do as well. In this sample project that has very few routes, the API and pages are a flat list of routes:

class Api {  constructor(express) {    this.express = express    this.i = 0  }  init() {    this.express.get("/api/get", (req, res) => {      res.send({  i: this.i })    })    this.express.post("/api/increment", (req, res) => {      this.i++      res.send({ i: this.i })    })  }}

Obviously this is just a minimal sample API - all it does is allows you to read an increment an integer stored in memory on the server.

class Pages {  constructor(express, next) {    this.express = express    this.next = next  }  init() {    this.initSpecialPages()    this.initDefaults()  }  initSpecialPages() {    this.express.get('/my_special_page/:special_value', (req, res) => {      const intValue = parseInt(req.params.special_value)      if(intValue) {        return this.next.render(req, res, `/special_int`, req.query)      } else {        return this.next.render(req, res, `/special_string`, req.query)      }    })  }  initDefaults() {    this.express.get('/', (req, res) => {      return this.next.render(req, res, `/main`, req.query)    })    this.express.get('*', (req, res) => {      return this.next.render(req, res, `${req.path}`, req.query)    })  }}

The page routes showcase setting up a root / path, and a fallback * path - if we're not able to match the GET request, we default to what NextJS's default behavior is: rendering pages by filename from the /pages directory. This allows for a gentle extension of NextJS's default capabilities.

I also added an example of a custom route that might not fit very well with NextJS's default routing. We support the /my_special_page/:special_value route, but if the :special_value is an integer, we render a special page that behaves quite differently from string-valued parameters.

Using the template

I licensed the code under MIT - which means you are free to use the template in closed-source and commercial products, and make and modifications you want. I'll of course appreciate any actual attribution.

It's also a template on github, which means you can just click a button and start a new repo based on https://github.com/alexey-dc/nextjs_express_template

Screen Shot 2021-06-20 at 5.36.59 PM

Running

The instructions for running are in the github repo. The server is set up to run in HTTPS in localhost, which I highly recommend - you can set up your certificates and environment following the project's README.

Other than that, the server should run with a simple yarn install, yarn start.

Iterating

You'll probably want to delete the sample custom endpoint and associated pages I provided - and start replacing them with your own!

I included a sample organization for pages as well - the page roots are in pages as nextjs mandates, but all the reusable jsx is in views - for the demo, I was using a common layout for pages, and the Layout component is housed in views.

I was trying to keep this project minimal, so I didn't include any custom CSS, or impose any sort of styling conventions - but you'll want to use either modular CSS or global CSS https://nextjs.org/docs/basic-features/built-in-css-support


Original Link: https://dev.to/alexeydc/express-nextjs-sample-tutorial-integration-485f

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To