Search code examples
node.jsexpressvue.jsxmlhttprequestexpress-router

XHR pre-flight with Express Node.js


I'm trying to make a "MEVN" application (MongoDB, Express, Vue, NodeJs).

On the client side, I have a vue component which provides a form to create a post. The post is basically a title and a description. So I have something like this :

methods: {
  async addPost () {
    await PostsService.addPost({
      title: this.title,
      description: this.description
    })
    this.$router.push({ name: 'Posts' })
  }
}

My PostsService is a helper to handle posts :

import Api from '@/services/Api'

export default {
  addPost (params) {
    return Api().post('posts', params)
  },
}

My Api is a simple axios constructor :

import axios from 'axios'

export default() => {
  return axios.create({
    baseURL: `http://localhost:8081`
  })
}

On the server side, I have an entry point :

var express = require('express')
  , app = express()

app.use(require('./controllers'))

app.listen(8081, function() {
  console.log('Listening on port 8081...')
})  

Which initiate a controller loader :

var express = require('express')
  , router = express.Router()

router.use('/posts', require('./posts'))

module.exports = router

The post controller looks like this :

var express = require('express')
  , router = express.Router()
  , Post = require('../models/post')

// Add new post
router.post('/posts', function(req, res) {
  var title = req.title;
  var description = req.description;
  post = Post.create(title, description);
  if(post) {
    res.send({flag: 'SUCCESS', content: post})
  } else {
    res.send({flag: 'ERROR', content: 'Failed to create the post'})
  }
})

module.exports = router

As you can see there is some action with models/post which will make a db connection and save the post.

When I start my server using npm start, I have the confirmation I'm listenning on port 8081.

When I call the addPost() function, I get two XHR requests :

First XHR

    Request URL: http://localhost:8081/posts
    Request Method: OPTIONS
    Status Code: 204 No Content
    Remote Address: [::1]:8081
    Referrer Policy: no-referrer-when-downgrade
    Access-Control-Allow-Headers: content-type
    Access-Control-Allow-Methods: GET,HEAD,PUT,PATCH,POST,DELETE
    Access-Control-Allow-Origin: *
    Connection: keep-alive
    Content-Length: 0
    Date: Sun, 15 Jul 2018 18:20:01 GMT
    Vary: Access-Control-Request-Headers
    X-Powered-By: Express
    Accept: */*
    Accept-Encoding: gzip, deflate, br
    Accept-Language: en,fr-FR;q=0.9,fr;q=0.8,en-US;q=0.7,en-GB;q=0.6
    Access-Control-Request-Headers: content-type
    Access-Control-Request-Method: POST
    Connection: keep-alive
    Host: localhost:8081
    Origin: http://localhost:8080
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36

Second XHR

    Request URL: http://localhost:8081/posts
    Request Method: POST
    Status Code: 404 Not Found
    Remote Address: [::1]:8081
    Referrer Policy: no-referrer-when-downgrade
    Access-Control-Allow-Origin: *
    Connection: keep-alive
    Content-Length: 145
    Content-Security-Policy: default-src 'self'
    Content-Type: text/html; charset=utf-8
    Date: Sun, 15 Jul 2018 18:20:01 GMT
    X-Content-Type-Options: nosniff
    X-Powered-By: Express
    Accept: application/json, text/plain, */*
    Accept-Encoding: gzip, deflate, br
    Accept-Language: en,fr-FR;q=0.9,fr;q=0.8,en-US;q=0.7,en-GB;q=0.6
    Connection: keep-alive
    Content-Length: 35
    Content-Type: application/json;charset=UTF-8
    Host: localhost:8081
    Origin: http://localhost:8080
    Referer: http://localhost:8080/posts/new
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36
    {title: "test", description: "test"}

The second XHR fails and tells me : Cannot POST /posts

Can you explain me what happens and how to fix it ?

Thanks


Solution

  • Your route handler is for /posts/posts so you don't actually have a route handler for just /posts. The first part of the /posts/posts path comes from here:

    router.use('/posts', require('./posts'))
    

    The second /posts that adds onto the first comes from:

    router.post('/posts', function(req, res) {...}
    

    These two are additive so the URL you have a handler for is /posts/posts.

    I'm guessing that the OPTIONS request works because you have some generic CORS middleware installed that is approving all routes when requested with OPTIONS. The second request fails because there is no route handler for just /posts, only one for /posts/posts.


    You can fix this by either changing this:

    router.use('/posts', require('./posts'))
    

    to:

    router.use(require('./posts'))
    

    Or, by changing this:

    router.post('/posts', function(req, res) {..}
    

    to this:

    router.post('/', function(req, res) {..}
    

    Which ones to choose depends upon whether you want every route on your post controller router to inherit /posts as the root of the path or whether you want to define multiple routes at the top level in your post controller router.