ExpressJS with NuxtJS middleware passing post data to page - express

Can someone help me understand how to pass data from post request to the nuxt page that is loaded. I dont know how to send the data to the page that will be loaded.
I want to be able to process the POST request, then send that data for usage on the following page. I am open to suggestions but I can't find proper documentation, tutorials or examples to accomplish this task.
I don't want to use axios here (with JSON type response), because I would prefer to send POST data and load new page. Therefor if page is reloaded, POST data must be submitted again.
const express = require('express')
const bodyParser = require('body-parser')
const { Nuxt, Builder } = require('nuxt')
const app = express()
const host = process.env.HOST || '127.0.0.1'
const port = process.env.PORT || 3000
// parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: false }))
// parse application/json
app.use(bodyParser.json())
app.set('port', port)
// Import and Set Nuxt.js options
let config = require('../nuxt.config.js')
config.dev = !(process.env.NODE_ENV === 'production')
async function start() {
// Init Nuxt.js
const nuxt = new Nuxt(config)
// Build only in dev mode
if (config.dev) {
const builder = new Builder(nuxt)
await builder.build()
}
// Routes added
app.post('/events/booking', function (req, res, next) {
console.log('REQUEST:', req.body)
res.set('eventId', req.body.eventId)
res.set('moreData', ['some', 'more', 'data'])
next()
})
// Give nuxt middleware to express
app.use(nuxt.render)
// Listen the server
app.listen(port, host)
console.log('Server listening on http://' + host + ':' + port) // eslint-disable-line no-console
}
start()

I believe the source of your issue is the disconnect between Nuxt's implementation of Express, the deprecation/version-conflicts of bodyParser middleware and/or the Node event system.
I would personally take a step back by removing the custom express routing, handle the body parsing yourself in the middleware and take advantage of the Vuex store.
store/index.js
export const state = () => ({
postBody: null,
postError: null
})
export const mutations = {
postBody: (state, postBody) => {
state.postBody = postBody;
},
postError: (state, postError) => {
state.postError = postError;
},
}
export const getters = {
postBody: state => state.postBody,
postError: state => state.postError,
}
middleware/index.js
export default ({req, store}) => {
if (process.server && req && req.method === 'POST') {
return new Promise((resolve, reject) => {
req.on('data', data => resolve(store.commit('postBody', JSON.parse(data))));
req.on('error', data => reject(store.commit('postError', JSON.parse(data))));
})
}
}
pages/index.vue
<template>
<div>
<h1>Test page</h1>
<div v-if="postBody">
<h2>post body</h2>
<p>{{postBody}}</p>
</div>
<div v-if="postError">
<h2>post error</h2>
<p>{{postError}}</p>
</div>
<div v-if="!postError && !postBody">
Please post JSON data to this URL to see a response
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
middleware: 'post-data',
computed: mapGetters({
postBody: 'postBody',
postError: 'postError'
})
}
</script>
Below is a live and working example project of the above. POST JSON data using a client app (Postman, web form, etc) to see the posted data rendered on the page.
Live Code: https://glitch.com/edit/#!/terrific-velociraptor
Live Example: https://terrific-velociraptor.glitch.me/

Related

How to perform actions before providing downloadable files in Vue

I want to be able to keep track of file downloads in a Vue project. The goal is to provide a url like mysite.com/some/path/file-name.txt/tracking-source, perform an action like send the path to tracking api, then serve the file at mysite.com/some/path/file-name.txt
I tried using a redirect but it doesn't seem to provide a file download, it just updates the path in the browser.
use a route that captures the "tracking-source" parameter and performs the necessary tracking action, and then serves the file using the sendFile method from the express library.
Here is an example of how to set up a route in a Vue project using the vue-router library:
import Vue from 'vue'
import Router from 'vue-router'
import path from 'path'
import express from 'express'
Vue.use(Router)
const router = new Router({
routes: [
{
path: '/some/path/:fileName/:trackingSource',
name: 'download-file',
component: {
beforeRouteEnter (to, from, next) {
const { params } = to
// Perform tracking action using the trackingSource parameter
// ...
// Serve the file
const filePath = path.join(__dirname, 'path/to/files', `${params.fileName}.txt`)
express.sendFile(filePath, (err) => {
if (err) next(err)
})
}
}
}
]
})
here the route captures the "fileName" nd "trackingSource" parameters from the URL, and uses the beforeRouteEnter navigation guard to perform the tracking action and serve the file.
without express you can do something like this
<template>
<div>
<a ref="downloadLink" :href="fileUrl" download>Download</a>
<button #click="downloadFile">Download</button>
</div>
</template>
<script>
export default {
data() {
return {
fileUrl: ''
}
},
methods: {
async downloadFile() {
const { params } = this.$route
const fileName = `${params.fileName}.txt`
const filePath = `/path/to/files/${fileName}`
const response = await fetch(filePath)
if (!response.ok) {
throw new Error(`Failed to fetch file: ${response.status}`)
}
const blob = await response.blob()
this.fileUrl = window.URL.createObjectURL(blob)
this.$refs.downloadLink.click()
}
}
}
</script>
Since I also store my files in the public/files directory of the vue project, I opted to not fetch it.
{
path: '/files/:fileName/:source',
redirect: to => {
const fileName = to.params.fileName
logEvent(analytics, fileName, {source: to.params.source});
const a = document.createElement('a');
document.body.appendChild(a);
a.href = `/files/${fileName}`;
a.download = fileName;
a.click();
setTimeout(() => {
window.URL.revokeObjectURL(a.href);
document.body.removeChild(a);
}, 0)
return {path: '/' }
}
}

Using async api into main.js of vue js

In main.js, from vue project app, I am setting a socket io url using the return of an API.
const url = getAPIAddress(params); //API promises
My problem is that main.js has no async function. So I canĀ“t use async/await.
How could I do that? Or Is there a better way to do it?
//main.js sample
import VueSocketIOExt from "vue-socket.io-extended";
import io from "socket.io-client";
import getAPIAddress from "getAPIAddress";
const url = getAPIAddress(params); //API promises
const socket = io(url, { autoConnect: false });
Vue.use(VueSocketIOExt, socket, { store });
Vue.config.productionTip = false;
Vue.use(VuetifyDialog, {
context: {
vuetify,
},
});
Hello you should try something like :
(async () => {
const url = await getAPIAddress(params); //API promises
// the rest of the code
})();

Accessing Vuex store in Nuxt project from JS file

In my Nuxt project I have a file named "apiAccess.js" in the root folder. This file simply exports a bunch of functions that make Ajax calls to the server API. This file is imported in any page that needs access to the server API. I need to send a JWT token with each of these api requests, and I have stored that token in the Vuex store.
I need to access the JWT token from the Vuex store within this "apiAccess.js" file. Unfortuntaely, this.$store is not recognized within this file. How do I access the Vuex store from within this file? Or should I have done something differently?
Here's a snippet from the apiAccessjs file where I try to access the store:
import axios from 'axios'
const client = axios.create({
baseURL: 'http://localhost:3000/api',
json: true,
headers: { Authorization: 'Bearer' + this.$store.state.auth.token }
})
After i readed this post i used this generic structure:
// generic actions file
import {
SET_DATA_CONTEXT,
SET_ITEM_CONTEXT
} from '#/types/mutations'
// PAGEACTIONS
export const getDataContext = api => async function ({ commit }) {
const data = await this[api].get()
commit(SET_DATA_CONTEXT, data)
}
export const getItemContext = api => async function ({ commit }, id) {
const data = await this[api].getById(id)
commit(SET_ITEM_CONTEXT, data)
}
export const createItemContext = api => async function ({}, form) {
await this[api].create(form)
}
export const updateItemContext = api => async function ({}, form) {
await this[api].update(form)
}
export const deleteItemContext = api => async function ({}, id) {
await this[api].delete(id)
}
and for any store i used actions from my generic file:
// any store file
import {
getDataContext,
getItemContext,
createItemContext,
updateItemContext,
deleteItemContext,
setDynamicModal
} from '#/use/store.actions'
const API = '$rasterLayerAPI'
export const state = () => ({
dataContext: [],
itemContext: {},
})
export const actions = {
createItemContext: createItemContext(API),
getDataContext: getDataContext(API),
getItemContext: getItemContext(API),
updateItemContext: updateItemContext(API),
deleteItemContext: deleteItemContext(API),
}
because I had many stores with similar features.
and the same for mutations i used generic mutations functions.

error hanlders not working properly in express together with nuxtjs

I am using Nuxtjs as a middleware in expressjs, and I have problems with handling errors in express server part.
When browser goes to 'localhost:3000', it will throw res.status is not a function error. when I comment that code block, everything is good expect that I cannot handle with uncaught server errors...
Any ideas?
import dotenv from 'dotenv'
dotenv.config({ silent: process.env.NODE_ENV === 'production', path: 'server/.env' })
import express from 'express'
import { ready } from 'consola'
import { Nuxt, Builder } from 'nuxt'
import cors from 'cors'
import { json, urlencoded } from 'body-parser'
import cookieParser from 'cookie-parser'
import passport from 'passport'
import './utils/auth'
import router from './routes'
const app = express()
app.use(json({ limit: '50mb' }))
app.use(urlencoded({ limit: '50mb', extended: true }))
app.use(cookieParser())
app.use(cors())
app.use(passport.initialize())
app.use('/api', router)
app.use('/api/*', (req, res) => {
res.status(404).end()
})
// error handlers
// it is problematic
// app.use((err, req, res) => {
// res.status(err.status || 500).end()
// })
let config = require('../nuxt.config.js')
config.dev = !(process.env.NODE_ENV === 'production')
async function start() {
const nuxt = new Nuxt(config)
if (config.dev) {
const builder = new Builder(nuxt)
await builder.build()
}
app.use(nuxt.render)
const host = process.env.HOST || '127.0.0.1'
const port = process.env.PORT || 3000
app.set('port', port)
app.listen(port, host)
ready({
message: `Server listening on http://${host}:${port}`,
badge: true,
})
}
start()
Without testing this code myself, I had a similar issue with my custom error handler and realized at one point I removed the next argument and it was failing. Can't say it will fix the issue because you are getting an error about res. I read through this page a few times and noticed a few mistakes I was making because I was using async functions.
Error Handling Guide may provide some help if you look over it carefully, but more specifically my comment about next is found under the title Writing error handlers
I wanted to say it might be arrow functions, but I don't see why that would break the logic here.

Is there a way to bootstrap an Express app?

I'm building an app in Express but I'd like it to call out to S3 to retrieve some keys before the server actually starts up. Is this possible in Express? If I google bootstrap Express I get hits for setting up Express with twitter Bootstrap.
I have used Sails.js before and you could specify bootstrap configurations in a bootstrap.js file so I guess I'm looking for something similar. Otherwise are there alternatives?
I have a index.js file and a separate bin/www file which calls the index.js file. I'd like the bootstrapping done in index.js so that it's included as part of the tests. Right now I 'initialize' the bootstrap but as it's asynchronous the server is already up and running before the bootstrap has complete (or errored out) i.e.
import express from 'express';
import {initializeFromS3} from './services/initializerService';
import healthCheckRouter from './routes/healthCheckRouter';
import bodyParser from 'body-parser';
initializeFromS3(); // Calls out to S3 and does some bootstrapping of configurations
const app = express();
app.use(bodyParser.json()); // to support JSON-encoded bodies
app.use(bodyParser.urlencoded({ // to support URL-encoded bodies
extended: true
}));
// ---------------------------- Routes ----------------------------
app.use('/', express.static('dist/client/'));
app.use('/health-check', healthCheckRouter);
export default app;
Posting my solution for anyone who comes across the same and has a mind blank. I kept the bin/www and index.js files separately but had the express object returned from index.js via a method. Solution below thanks to the friendly people of Github.
Index.js file:
import express from 'express';
import {initialize} from './services/appService';
import healthCheckRouter from './routes/healthCheckRouter';
import loginRouter from './routes/loginRouter';
export function getExpress() {
return initialize()
.then(() => {
const app = express();
// ---------------------------- Routes ----------------------------
app.use('/', express.static('dist/client/'));
app.use('/login', loginRouter);
app.use('/health-check', healthCheckRouter);
return app;
})
}
bin/www file:
import winston from 'winston';
import bodyParser from 'body-parser';
import {getExpress} from '../index';
getExpress()
.then(app => {
app.use(bodyParser.json()); // to support JSON-encoded bodies
app.use(bodyParser.urlencoded({ // to support URL-encoded bodies
extended: true
}));
const port = 3002;
app.listen(port, () => {
winston.info(`Server listening on port ${port}!`);
});
})
.catch(err => {
winston.error('Error starting server', err);
});
Integration tests:
import request from 'supertest';
import {getExpress} from '../../index'
describe('/login integration test', () => {
let app = null;
beforeEach(done => {
getExpress()
.then(res => {
app = res;
done();
});
});
describe('GET /login', () => {
it('should return 400 error if \'app\' is not provided as a query string', done => {
request(app)
.get('/login')
.expect(400, done);
});
});
});