Reputation: 358
I have been trying for weeks to get Nuxt.js + Express (using create-nuxt-app
) to work with a 3rd-party API. No matter what I try, nothing, absolutely nothing, works. No console statement from my server-side gets triggered, only the front-end statements. Also, I recently started getting Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
and I'm not setting headers anywhere in my code.
All I want to do is take user input, pass it through to my back-end to avoid CORS issues, get data back from the 3rd-party API, and then display it. It's simple. It happens all the time on the internet, so why can't I get it to work?
Below are all the files involved in the process described above.
pages/index.vue:
<template>
<div class="container">
<img src="@/assets/images/OscarPark.jpg" alt="Oscar in the park" title="I love the park!" />
<br />
<form>
<label for="title">Book Title:</label>
<input v-model="titleFromUser" type="text" name="title" class="title" />
<button @click.prevent="submit" class="submit">Find a Book!</button>
</form>
</div>
</template>
<script>
import { mapState } from 'vuex';
export default {
components: {},
data() {
return {
titleFromUser: '',
};
},
computed: mapState(['newTitles']),
methods: {
submit() {
this.$store.dispatch('FETCH_BOOK_TITLES', this.titleFromUser);
this.titleFromUser = '';
},
},
};
</script>
<style lang="scss">
.container {
margin: 0 auto;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
img {
transform: rotate(90deg); // this fixes a glitch that causes image to rotate needlessly
padding-top: $underHeaderGap; // these 2 padding lines help to fix the placement of the image caused by the needless rotation
padding-left: $underHeaderGap * 5;
margin-bottom: $underHeaderGap * 5;
height: $imgHeight;
}
}
</style>
store/index.js:
import consola from 'consola';
export const state = () => ({
newTitles: [],
});
export const mutations = {
SET_NEW_TITLES(state, newTitles) {
state.newTitles = newTitles;
},
};
export const actions = {
async FETCH_BOOK_TITLES({ commit }, titleFromUser) {
try {
consola.ready({
message: `'FETCH_BOOK_TITLES': titleFromUser: ${titleFromUser}`,
badge: true,
});
const { data } = await this.$axios.$post('/title', { titleFromUser });
consola.ready({
message: `data returned from api: ${data}`,
badge: true,
});
commit('SET_NEW_TITLES', data);
} catch (error) {
consola.error({
message: `FETCH_BOOK_TITLES: Something went wrong: ${error}`,
badge: true,
});
throw new Error(error);
}
},
};
server/index.js:
const express = require('express');
const cors = require('cors');
const consola = require('consola');
const axios = require('axios');
const { Nuxt, Builder } = require('nuxt');
const app = express();
const jsonParser = express.json();
const titleRouter = require('../api/title/index');
// 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;
// Give app ability to parse json
app.use(jsonParser);
// Give app ability to get past CORS issues
app.use(cors());
// Give nuxt middleware to express
app.use(nuxt.render);
// Build only in dev mode
if (config.dev) {
const builder = new Builder(nuxt);
await builder.build();
} else {
await nuxt.ready();
}
app.use('/title', titleRouter);
app.get('/title', (req, res) => {
consola.ready({
message: `res.json in title endpoint-server: ${res.json()}`,
badge: true,
});
consola.ready({
message: `req.json in title endpoint-server: ${req.json()}`,
badge: true,
});
const recommendationsURL = `https://tastedive.com/api/similar?q=and+then+there+were+none&type=books&info=1&k=${process.env.TASTE_DIVE_API_KEY}`;
axios
.get(recommendationsURL, (req, res) => {
consola.ready({
message: `from server/index.js: ${res.json()}`,
badge: true,
});
})
.catch((error) => {
consola.error({
message: `error from axios server ${error} `,
badge: true,
});
});
});
// Listen to the server
app.listen(port, host, () => {
consola.ready({
message: `Server listening on http://${host}:${port}`,
badge: true,
});
});
}
start();
api/title/index.js:
const consola = require('consola');
const express = require('express');
const app = express();
const titleRouter = express.Router();
titleRouter.use((req, res, next) => {
Object.setPrototypeOf(req, app.request);
Object.setPrototypeOf(res, app.response);
req.res = res;
res.req = req;
next();
});
titleRouter.get('/title', (req, res) => {
res
.json()
.then((data) => {
consola.ready({
message: `~api/title get title is ${data}`,
badge: true,
});
})
.catch((error) => {
consola.error({
message: `~api/title get Something went wrong: ${error}`,
badge: true,
});
throw new Error(error);
});
});
titleRouter.post('/title', (req, res) => {
res
.json()
.then((data) => {
consola.ready({
message: `~api/title post title is ${data}`,
badge: true,
});
})
.catch((error) => {
consola.error({
message: `~api/title post Something went wrong: ${error}`,
badge: true,
});
throw new Error(error);
});
});
module.exports = titleRouter;
nuxt.config.js:
require('dotenv').config();
module.exports = {
mode: 'universal',
/*
** Headers of the page
*/
head: {
title: "Oscar's Book Recommendations",
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{
hid: 'description',
name: 'description',
content: process.env.npm_package_description || '',
},
],
link: [{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }],
},
/*
** Customize the progress-bar color
*/
loading: { color: '#fff' },
/*
** Plugins to load before mounting the App
*/
plugins: [],
/*
** Nuxt.js dev-modules
*/
buildModules: [
// Doc: https://github.com/nuxt-community/eslint-module
'@nuxtjs/eslint-module',
'@nuxtjs/dotenv',
'@nuxtjs/style-resources',
],
/*
** Nuxt.js modules
*/
modules: [
// Doc: https://axios.nuxtjs.org/usage
'@nuxtjs/axios',
'@nuxtjs/pwa',
'@nuxtjs/auth',
],
/*
** Axios module configuration
** See https://axios.nuxtjs.org/options
*/
axios: {
https: true,
baseURL: process.env.BASE_URL || 'http://localhost:3000',
},
/*
** Build configuration
*/
build: {
watch: ['api/title'],
/*
** You can extend webpack config here
*/
extend(config, ctx) {},
},
pageTransition: {
name: 'fade',
mode: 'out-in',
},
env: {
TASTE_DIVE_API_KEY: process.env.TASTE_DIVE_API_KEY,
},
serverMiddleware: ['~api/title'],
styleResources: {
scss: ['~assets/styles/main.scss'],
},
};
Does anybody see what I'm not? Why isn't the data going through to the server? Why is it telling me that I can't set headers after they're being sent when I'm not setting them anywhere in my code? What is wrong with this?
I would greatly appreciate anybody's input and help. Thanks.
Upvotes: 0
Views: 1593
Reputation: 358
Ok, so it happens that I had a server/index.js
and a nuxt.config.js
with a serverMiddleware
entry. Sebastien Chopin, one of the creators of Nuxt, came across my tweet about this issue and kindly pointed out that I can use either the server/index.js
file OR the serverMiddleware
in my nuxt.config.js
, but NOT both as I had been doing. As somebody who first learned Express by creating server files, I can easily see how this would lead somebody into confusion.
In the end, I settled for using the Nuxt way of doing it and used the serverMiddleware
in my nuxt.config.js
. When you set up a Nuxt project and choose to have Express integrated from the start, this is the preferred way of using Express, as opposed to the traditional server files.
Upvotes: 2