Guide Create Chat Socket.io SSR Nuxt app
Boilerplates for Express and Fastify.
ExpressJS
Understanding the basic way for enabling socket.io in Nuxt Express app and use Vue.observable as global store.
Generate Nuxt Express app from command
npx create-nuxt-app chat-express-nuxt
Output:
create-nuxt-app v2.15.0
✨ Generating Nuxt.js project in chat-express-nuxt
? Project name chat-express-nuxt
? Project description Chat Express Nuxt
? Author name Harianto
? Choose programming language JavaScript
? Choose the package manager Npm
? Choose UI framework None
? Choose custom server framework Express
? Choose Nuxt.js modules Axios, Progressive Web App (PWA) Support
? Choose linting tools Prettier
? Choose test framework None
? Choose rendering mode Universal (SSR)
? Choose development tools jsconfig.json (Recommended for VS Code)
- chat-express-nuxt
- Chat Express Nuxt
- Harianto
- JavaScript
- npm
- None
- Express
- Axios, PWA
- Prettier
- None
- SSR
- jsconfig.json
Optional
cd chat-express-nuxt
# Save Node version to a file
node -v > .nvmrc
# optional
git add .
git commit -avm "INIT Express PWA SSR Nuxt - Node $(node -v) | NPM $(npm -v)"
Socket.io
Add socket.io from NPM and SASS
npm i -S socket.io axios
npm i -D node-sass sass-loader
Apply socket.io
We require http and use that to create server instead of express from the default generated project.
I highlight the lines of code in the snippet below:
const http = require('http'),
express = require('express'),
app = express(),
{ Router } = express,
server = http.createServer(app),
io = require('socket.io')(server)
async function start() {
app.use(express.json()) // Receive Header: Content-Type: application/json
app.use(express.urlencoded({ extended: false })) // Parse Body to JSON
app.use(nuxt.render)
// Listen the server
server.listen(port, host)
}
server/index.js
Becomes:
const http = require('http'),
express = require('express'),
app = express(),
{ Router } = express,
server = http.createServer(app),
io = require('socket.io')(server)
const consola = require('consola')
const { Nuxt, Builder } = require('nuxt')
// Import and Set Nuxt.js options
const config = require('../nuxt.config.js')
config.dev = process.env.NODE_ENV !== 'production'
async function start() {
// Init Nuxt.js
const nuxt = new Nuxt(config)
const { host, port } = nuxt.options.server
await nuxt.ready()
// Build only in dev mode
if (config.dev) {
const builder = new Builder(nuxt)
await builder.build()
}
app.use(express.json())
app.use(express.urlencoded({ extended: false }))
// // Give nuxt middleware to express
app.use(nuxt.render)
// Listen the server
server.listen(port, host)
consola.ready({
message: `Server listening on http://${host}:${port}`,
badge: true
})
}
start()
server/index.js
Start the server and check socket.io
npm run dev
Integrating socket.io
Router: message
module.exports = ({ Router, io }) => {
const router = Router()
/* GET users listing. */
router
.get('/', function(req, res, next) {
res.json({
message: true
})
})
.post('/', (req, res) => {
io.emit('chat-message', { ...req.body, id: 'POST' })
res.json(req.body)
})
return router
}
api/router/message.js
API: index
module.exports = ({ Router, io }) => {
const router = Router()
/* GET users listing. */
router
.get('/', function(req, res, next) {
res.json({
api: true
})
})
// Router: message
.use('/message', require('./router/message')({ Router, io }))
io.on('connection', socket => {
console.log('Socket Connect:', { id: socket.id })
io.emit('chat-message', { id: socket.id, status: 'ENTERS ROOM' })
socket.on('disconnect', message => {
console.log({ id: socket.id, message })
socket.broadcast.emit('chat-message', { id: socket.id, status: 'LEFT' })
})
socket.on('chat-message', msg => {
console.log('chat-message:', msg)
io.emit('chat-message', { id: socket.id, message: msg })
})
})
return router
}
api/index.js
Connect Express api Router
app.use('/api', require('../api')({ Router, io }))
server/index.js
Becomes:
const http = require('http'),
express = require('express'),
app = express(),
{ Router } = express,
server = http.createServer(app),
io = require('socket.io')(server)
const consola = require('consola')
const { Nuxt, Builder } = require('nuxt')
// Import and Set Nuxt.js options
const config = require('../nuxt.config.js')
config.dev = process.env.NODE_ENV !== 'production'
async function start() {
// Init Nuxt.js
const nuxt = new Nuxt(config)
const { host, port } = nuxt.options.server
await nuxt.ready()
// Build only in dev mode
if (config.dev) {
const builder = new Builder(nuxt)
await builder.build()
}
app.use(express.json())
app.use(express.urlencoded({ extended: false }))
app.use('/api', require('../api')({ Router, io }))
// Give nuxt middleware to express
app.use(nuxt.render)
// Listen the server
server.listen(port, host)
consola.ready({
message: `Server listening on http://${host}:${port}`,
badge: true
})
}
start()
server/index.js
Add local script
export default {
head() {
return {
script: [{ src: '/socket.io/socket.io.js' }]
}
}
}
pages/index.vue
<template>
<div class="container">
<div>
<logo />
<h1 class="title">chat-express-nuxt</h1>
<h2 class="subtitle">Chat Express Nuxt</h2>
<div class="links">
<a href="https://nuxtjs.org/" target="_blank" class="button--green">Documentation</a>
<a href="https://github.com/nuxt/nuxt.js" target="_blank" class="button--grey">GitHub</a>
</div>
</div>
</div>
</template>
<script>
import Logo from '~/components/Logo.vue'
export default {
head() {
return {
script: [{ src: '/socket.io/socket.io.js' }]
}
},
components: {
Logo
}
}
</script>
<style>
.container {
margin: 0 auto;
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
text-align: center;
}
.title {
font-family: 'Quicksand', 'Source Sans Pro', -apple-system, BlinkMacSystemFont,
'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
display: block;
font-weight: 300;
font-size: 100px;
color: #35495e;
letter-spacing: 1px;
}
.subtitle {
font-weight: 300;
font-size: 42px;
color: #526488;
word-spacing: 5px;
padding-bottom: 15px;
}
.links {
padding-top: 15px;
}
</style>
pages/index.vue
Alternative global settings
export default {
head: {
script: [
{ src: '/socket.io/socket.io.js' }
]
}
}
nuxt.config.js
Page Layout
Store: messages
import Vue from 'vue'
const state = Vue.observable({
messages: []
}),
methods = {
onMessage(message) {
state.messages.push(message)
}
}
export { state, methods }
lib/store/storeMessages.js
Page
<template>
<div class="app-tpl">
<h1>{{title}}</h1>
<input v-model="formData.message" placeholder="Message" />
<div>
<button @click="onEmit">Emit Message</button>
<button @click="onPost">Post Message</button>
</div>
<ul class="messages">
<li v-for="msg in messages">
<pre v-text="msg" />
</li>
</ul>
</div>
</template>
<script>
import Logo from '~/components/Logo.vue'
import { state, methods } from '~/lib/store/storeMessages'
import axios from 'axios'
const post = (url, data) => axios.post(url, data).then(({ data }) => data)
let socket
export default {
head() {
return {
script: [{ src: '/socket.io/socket.io.js' }]
}
},
components: {
Logo
},
data: () => ({
title: 'Chat Nuxt App',
formData: {
message: ''
}
}),
computed: {
messages() {
return state.messages
}
},
methods: {
onEmit() {
const { formData } = this
socket.emit('chat-message', formData.message)
console.log('onEmit:', formData.message, socket.id)
formData.message = ''
},
onPost() {
const { formData } = this,
url = '/api/message'
console.log('onPost:', formData.message)
post(url, {
id: socket.id,
message: formData.message
})
.then(console.log.bind(console, 'RESPONSE:'))
.catch(console.error.bind(console, 'FAIL - onPost:'))
formData.message = ''
}
},
// Life Cycle Hooks
mounted() {
socket = io()
socket.on('chat-message', methods.onMessage)
}
}
</script>
<style lang="scss">
body {
margin: 0;
padding: 0;
}
.app-tpl {
height: calc(100vh);
overflow-y: auto;
overflow-x: hidden;
background-color: rgb(248, 239, 201);
}
.messages {
list-style: none;
margin: 0;
padding: 0;
li {
&:nth-child(odd) {
background-color: yellow;
}
}
}
</style>
pages/index.vue
FastifyJS
Generate Nuxt Fastify app from command
npx create-nuxt-app chat-fastify-nuxt
Output:
create-nuxt-app v2.15.0
✨ Generating Nuxt.js project in chat-fastify-nuxt
? Project name chat-fastify-nuxt
? Project description Chat Fastify Nuxt
? Author name Harianto
? Choose programming language JavaScript
? Choose the package manager Npm
? Choose UI framework None
? Choose custom server framework Fastify
? Choose Nuxt.js modules Axios, Progressive Web App (PWA) Support
? Choose linting tools Prettier
? Choose test framework None
? Choose rendering mode Universal (SSR)
? Choose development tools jsconfig.json (Recommended for VS Code)
- chat-fastify-nuxt
- Chat Fastify Nuxt
- Harianto
- JavaScript
- Npm
- None
- Fastify
- Axios, WPA
- Prettier
- None
- SSR
- jsconfig.json
Optional
cd chat-fastify-nuxt
# Save Node version to a file
node -v > .nvmrc
# optional
git add .
git commit -avm "INIT Fastify PWA SSR Nuxt - Node $(node -v) | NPM $(npm -v)"
Socket.io
Add socket.io from NPM and SASS
npm i -S socket.io axios
npm i -D node-sass sass-loader
Apply socket.io
We can use fastify.server
to connect our socket.io.
Like so:
const io = require('socket.io')(fastify.server)
server/index.js
const { Nuxt, Builder } = require('nuxt')
const fastify = require('fastify')({
logger: true
}),
io = require('socket.io')(fastify.server)
// Import and Set Nuxt.js options
const config = require('../nuxt.config.js')
config.dev = process.env.NODE_ENV !== 'production'
async function start() {
// Instantiate nuxt.js
const nuxt = new Nuxt(config)
const {
host = process.env.HOST || '127.0.0.1',
port = process.env.PORT || 3000
} = nuxt.options.server
await nuxt.ready()
// Build only in dev mode
if (config.dev) {
const builder = new Builder(nuxt)
await builder.build()
}
fastify.use(nuxt.render)
fastify.listen(port, host, (err, address) => {
if (err) {
fastify.log.error(err)
process.exit(1)
}
})
}
start()
server/index.js
Start the server and check socket.io
npm run dev
Integrating socket.io
Router: message
module.exports = io => async (fastify, options) => {
fastify
// Route: /
.get('/', async () => ({ message: true }))
.post('/', {}, async (request, reply) => {
io.emit('chat-message', {
...request.body,
id: `POST: ${request.body.id}`
})
return request.body
})
}
api/routes/message.js
Api: index
module.exports = io => async (fastify, options) => {
fastify
.get('/', async () => ({ api: true }))
// register
.register(require('./routes/message')(io), { prefix: '/message' })
io.on('connection', socket => {
console.log('Socket Connect:', { id: socket.id })
io.emit('chat-message', { id: socket.id, status: 'ENTERS ROOM' })
socket.on('disconnect', message => {
console.log({ id: socket.id, message })
socket.broadcast.emit('chat-message', { id: socket.id, status: 'LEFT' })
})
socket.on('chat-message', msg => {
console.log('chat-message:', msg)
io.emit('chat-message', { id: socket.id, message: msg })
})
})
}
api/index.js
Connect fastify api Router
fastify.register(require('../api')(io), { prefix: '/api' })
fastify.setNotFoundHandler(({ req }, { res }) => nuxt.render(req, res))
server/index.js
Replace: fastify.use(nuxt.render)
const { Nuxt, Builder } = require('nuxt')
const fastify = require('fastify')({
logger: true
}),
io = require('socket.io')(fastify.server)
// Import and Set Nuxt.js options
const config = require('../nuxt.config.js')
config.dev = process.env.NODE_ENV !== 'production'
async function start() {
// Instantiate nuxt.js
const nuxt = new Nuxt(config)
const {
host = process.env.HOST || '127.0.0.1',
port = process.env.PORT || 3000
} = nuxt.options.server
await nuxt.ready()
// Build only in dev mode
if (config.dev) {
const builder = new Builder(nuxt)
await builder.build()
}
fastify.register(require('../api')(io), { prefix: '/api' })
fastify.setNotFoundHandler(({ req }, { res }) => nuxt.render(req, res))
fastify.listen(port, host, (err, address) => {
if (err) {
fastify.log.error(err)
process.exit(1)
}
})
}
start()
server/index.js
Add local script
export default {
head() {
return {
script: [{ src: '/socket.io/socket.io.js' }]
}
}
}
pages/index.vue
<template>
<div class="container">
<div>
<logo />
<h1 class="title">chat-nuxt</h1>
<h2 class="subtitle">Chat Nuxt</h2>
<div class="links">
<a href="https://nuxtjs.org/" target="_blank" class="button--green">Documentation</a>
<a href="https://github.com/nuxt/nuxt.js" target="_blank" class="button--grey">GitHub</a>
</div>
</div>
</div>
</template>
<script>
import Logo from '~/components/Logo.vue'
export default {
head() {
return {
script: [{ src: '/socket.io/socket.io.js' }]
}
},
components: {
Logo
}
}
</script>
<style>
.container {
margin: 0 auto;
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
text-align: center;
}
.title {
font-family: 'Quicksand', 'Source Sans Pro', -apple-system, BlinkMacSystemFont,
'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
display: block;
font-weight: 300;
font-size: 100px;
color: #35495e;
letter-spacing: 1px;
}
.subtitle {
font-weight: 300;
font-size: 42px;
color: #526488;
word-spacing: 5px;
padding-bottom: 15px;
}
.links {
padding-top: 15px;
}
</style>
pages/index.vue
Alternative global settings
export default {
head: {
script: [
{ src: '/socket.io/socket.io.js' }
]
}
}
nuxt.config.js
Page Layout
Store: messages
import Vue from 'vue'
const state = Vue.observable({
messages: []
}),
methods = {
onMessage(message) {
state.messages.push(message)
}
}
export { state, methods }
lib/store/storeMessages.js
Page
<template>
<div class="app-tpl">
<h1>{{title}}</h1>
<input v-model="formData.message" placeholder="Message" />
<div>
<button @click="onEmit">Emit Message</button>
<button @click="onPost">Post Message</button>
</div>
<ul class="messages">
<li v-for="msg in messages">
<pre v-text="msg" />
</li>
</ul>
</div>
</template>
<script>
import Logo from '~/components/Logo.vue'
import { state, methods } from '~/lib/store/storeMessages'
import axios from 'axios'
const post = (url, data) => axios.post(url, data).then(({ data }) => data)
let socket
export default {
head() {
return {
script: [{ src: '/socket.io/socket.io.js' }]
}
},
components: {
Logo
},
data: () => ({
title: 'Chat Nuxt App',
formData: {
message: ''
}
}),
computed: {
messages() {
return state.messages
}
},
methods: {
onEmit() {
const { formData } = this
socket.emit('chat-message', formData.message)
console.log('onEmit:', formData.message, socket.id)
formData.message = ''
},
onPost() {
const { formData } = this,
url = '/api/message'
console.log('onPost:', formData.message)
post(url, {
id: socket.id,
message: formData.message
})
.then(console.log.bind(console, 'RESPONSE:'))
.catch(console.error.bind(console, 'FAIL - onPost:'))
formData.message = ''
}
},
// Life Cycle Hooks
mounted() {
socket = io()
socket.on('chat-message', methods.onMessage)
}
}
</script>
<style lang="scss">
body {
margin: 0;
padding: 0;
}
.app-tpl {
height: calc(100vh);
overflow-y: auto;
overflow-x: hidden;
background-color: rgb(248, 239, 201);
}
.messages {
list-style: none;
margin: 0;
padding: 0;
li {
&:nth-child(odd) {
background-color: yellow;
}
}
}
</style>
pages/index.vue
Next
Other Nuxt SSR configurations
Babel sourceType
Make CommonJS and ES modules a love couple.
module.exports = {
build: {
babel: {
sourceType: 'unambiguous'
}
}
}
nuxt.config.js