Creating a Nuxt application with Koa, Express, and Slim
Introduction
Nuxt is a framework for creating Universal Vue Applications, aka isomorphic JavaScript Applications. It is similar to Next for React and Angular Universal for Angular. Personally I find the code written in React is difficult to read, while Angular has a steap learning curl. Vue is just right. It is easy to understand and to pick up. Nuxt is created for writing Vue on the server side. So you can replace your current template engine, e.g. Pug and Handlebars for Express and Koa, Twig for Slim or any other server database driven applications. Nuxt is more than just a template for your application, notably, it provides a routing system that is just right out of the box for you. It comes a configurable directory structure so you don't have to struggle on that level.
However, when it come to isomorphic applications, the distinction between client-side and server-side seems to have become blur. Let's conider Nuxt (or React) as frontend server-side, while Slim (or others) as backend server-side. We still can consider Nuxt as client-side since it deals with the client.
Frontend server-side
One of the benefits writing isomorphic JavaScript is that you can share your code, such as a function for client-side and server-side at the same time. And that you don't have to duplicate your code. There are benefits of using Nuxt (or React). What I like about it is that you can decouple your template and server application for good - if you ever wonder how to decouple the view and controller in your application. You can read the post I wrote for PHP applications. This article mainly is to show how you can do it for Koa and Expre applications.
Here is a welcome page in Nuxt:
// pages/index.vue
<template>
<div>
<h1>Welcome!</h1>
<nuxt-link to="/about">About page</nuxt-link>
</div>
</template>
And the about page:
// pages/about.vue
<template>
<div>
<p>Hi from {{ name }}</p>
<nuxt-link to="/">Home page</nuxt-link>
</div>
</template>
<script>
export default {
asyncData ({ isStatic, isServer }) {
return {
name: isStatic ? 'static' : (isServer ? 'server' : 'client')
}
}
}
</script>
Backend Server Side
When come to backend server side application to serve Nuxt application, you can choose:
1. Koa
Koa stepped into the Node framework scene in 2014. The latest version at this point of writing is Koa 2. I never used first generation of Koa before but the main difference between koa and koa 2 is that koa 2 supports async/await. Koa helps you to get rid of the notorious callback hell.
Here is a "hello world" in koa 1:
var koa = require('koa')
var app = koa()
// uses generators
app.use(function *(){
this.body = 'Hello world'
})
app.listen(3000)
Koa 2:
import Koa from 'koa'
var app = new Koa()
// uses async functions
app.use(async (ctx) => {
ctx.body = 'Hello world!'
})
app.listen(3000)
2. Express
Express has a longer history than Koa. It has a larger community than Koa. The problem I stumble upon Express is that you could slip into the callback hell if you are not careful on planning your application.
Here is a "hello world" in Express:
const express = require('express')
const app = express()
app.get('/', function (req, res) {
res.send('Hello World!')
})
app.listen(3000, function () {
console.log('Example app listening on port 3000!')
})
3. Slim
Slim is a PHP micro framework that helps you quickly write simple yet powerful web applications and APIs. It is lightweight and easy to get your project started and going without too much dependencies. It is similar to Lumen by Laravel and Silex which is based on Symfony.
Here is a "hello world" in Slim:
use \Psr\Http\Message\ServerRequestInterface as Request;
use \Psr\Http\Message\ResponseInterface as Response;
require '../vendor/autoload.php';
$app = new \Slim\App;
$app->get('/', function (Request $request, Response $response) {
$response->getBody()->write("Hello World!");
return $response;
});
$app->run();
The concept
Nuxt runs as the view for the backend and we only use Nuxt routes for public users. axios is used inside Nuxt pages to call the backend routes. Then the backend spits JSON data only to the pages in Nuxt.
There are two architectures to approach the concept above: single-port and double-port. Below are the basic directory structures for each of them:
Single-port:
---------------
assets/
components/
layouts/
pages/
plugins/
server/
config/
index.js
Mlewares.js
routes.js
static/
backpack.config.js
nuxt.config.js
package.json
---------------
You write your backend application, whether it is Koa or Express inside server/
directory.
Multi-port:
---------------
client-nuxt/
assets/
components/
layouts/
pages/
plugins/
static/
backpack.config.js
nuxt.config.js
package.json
server-koa/
server/
config/
index.js
middlewares.js
routes.js
static/
backpack.config.js
package.json
---------------
Personally I prefer multi-port for its clarity. So that Nuxt does not interfere with the backend application. And you don't have to take care of two types of application at the same time. You can fully concentrate on the either side and don't have to worry which might break each other up.
Single-port
If you want to go for the single-port option, you need to glue Nuxt into these applications first. Unfortunately we cannot glue PHP with Nuxt, so it has to be a double-port option.
1. Koa
'use strict'
import Koa from 'koa'
import { Nuxt, Builder } from 'nuxt'
import config from './config'
import middlewares from './middlewares'
const app = new Koa()
const host = process.env.HOST || '127.0.0.1'
const port = process.env.PORT || config.server.port
// Import and Set Nuxt.js options
let nuxtConfig = require('../nuxt.config.js')
nuxtConfig.dev = !(app.env === 'production')
// Instantiate nuxt.js
const nuxt = new Nuxt(nuxtConfig)
// Build in development
if (nuxtConfig.dev) {
const builder = new Builder(nuxt)
builder.build().catch(e => {
console.error(e) // eslint-disable-line no-console
process.exit(1)
})
}
// Middlewares are imported here.
middlewares(app)
// Hook Nuxt up!
// https://github.com/nuxt-community/koa-template/blob/master/template/server/index.js
app.use(ctx => {
ctx.status = 200 // koa defaults to 404 when it sees that status is unset
return new Promise((resolve, reject) => {
ctx.res.on('close', resolve)
ctx.res.on('finish', resolve)
nuxt.render(ctx.req, ctx.res, promise => {
// nuxt.render passes a rejected promise into callback on error.
promise.then(resolve).catch(reject)
})
})
})
app.listen(port, host)
2. Express
'use strict'
import express from 'express'
import { Nuxt, Builder } from 'nuxt'
import config from './config'
import middlewares from './middlewares'
const app = express()
const host = process.env.HOST || '127.0.0.1'
const port = process.env.PORT || 3000
app.set('port', port)
// Middlewares are imported here.
middlewares(app)
// Import and Set Nuxt.js options
let configNuxt = require('../nuxt.config.js')
configNuxt.dev = !(process.env.NODE_ENV === 'production')
// Init Nuxt.js
const nuxt = new Nuxt(configNuxt)
// Build only in dev mode
if (configNuxt.dev) {
const builder = new Builder(nuxt)
builder.build()
}
// Give nuxt middleware to express
app.use(nuxt.render)
// Listen the server
app.listen(port, host)
console.log('Server listening on ' + host + ':' + port)
Multi-port
If you want to go for this option, you don't have to worry about gluing setup above. Just carry on what you have already been doing on your backend side as long as it sends the appropriate output/ data (e.g. JSON) for the frontend side.
Using the applications
You can download or clone the basic applications above in GitHub: Nuxt + Koa, Nuxt + Express, and Nuxt + Slim. Each repository contains single-port and double-port samples (Slim has double-port samples only) .
I use Nuxt + Koa as an example below:
1. Single-port
Go to the project root directory, install the packages and run the app:
# Install dependencies
$ npm install
# Serve the app for development
$ npm run dev
# Serve the app for production
$ npm start
# Test the app
$ npm run test
Access it at http://localhost:3000/
. You should see a home page with Hello World message which coming from the same port at http://localhost:3000/api
.
If you access the app at http://localhost:3000/users
, you should see a list of mock users coming from http://localhost:3000/api/users
.
1. Multi-port
- Go to the
server-koa/
directory, install the packages and run the app:
# Dependencies
$ npm install
# Development
$ npm run dev
# Production build
$ npm start
Access it at http://localhost:3030/
- Go to the
client-nuxt/
directory, install the packages and run the app:
# Dependencies
$ npm install
# Development
$ npm run dev
# Production build
$ npm start
Access it at http://localhost:3000/
. You should see a home page with Hello World message coming from the separate port at http://localhost:3030/
.
If you access the app at http://localhost:3000/users
, you should see a list of mock users coming from http://localhost:3030/users
.
Conclusion
I prefer the multi-port option as I have mentioned above. It has another advantage - you can create many micro backend applications as you wish and pipe them to a single Nuxt application. The backend applications can be written in JavaScript, PHP, Python, or any other languages as long as you set up CORS correctly. I hope you find this idea interesting and this article useful. Let me know what you think and what isomorphic technologies you use for your projects. Any suggestions and corrections, please leave a comment below.