jstrother
jstrother

Reputation: 358

Cannot get Nuxt.js, Express, and 3rd-party API to work together

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

Answers (1)

jstrother
jstrother

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

Related Questions