GraphQL - POST body missing. Did you forget use body-parser middleware? - node.js

I keep getting the following error on my graphql queries and not sure why:
POST body missing. Did you forget use body-parser middleware?
Am I doing something weird here? I have tried different recommendations with body-parser online, but still can't seem to fix it.
Server:
require('babel-polyfill')
const express = require('express')
const router = require('./middleware')
const expressStaticGzip = require('express-static-gzip')
const app = express()
const port = process.env.EXPRESS_PORT || 4000
const bodyParser = require('body-parser')
app.use(/\/((?!graphql).)*/, bodyParser.urlencoded({ extended: true }))
app.use(/\/((?!graphql).)*/, bodyParser.json())
app.use('/search/data', expressStaticGzip('public'))
app.use('/', router)
app.listen(port, () => {
console.log(`Server is running on port ${port}`)
})
Router
const router = express.Router()
const server = new ApolloServer({
typeDefs,
resolvers,
context: ({ req }) => {
const { authorization = '' } = req.headers
const universalFetch = (url, opts = {}) => {
return fetch(url, {
...opts,
headers: {
...opts.headers,
authorization,
},
})
}
const request = createRpcClient(universalFetch)
const methods = {}
const catalog = Object.keys(methods).reduce((catalog, method) => {
catalog[method] = params => request(methods[method], params)
return catalog
}, {})
return { catalog, fetch: universalFetch }
},
})
router.use(bodyParser.json())
router.use(bodyParser.text({ type: 'application/graphql' }))
router.use('*', renderer)
server.applyMiddleware({ app: router })

In my particular case the client just missed "Content-type" header with 'application/json' value. After adding that the error message has dissapeared.

applyMiddleware already adds body-parser for the GraphQL endpoint -- there's no need to apply it again and doing so may be causing your issue.
Additionally, I would expect applyMiddleware to be called before router.use('*', renderer) -- otherwise, I would think the wildcard route would be used for /graphql as well?

I forgot the header content-type: application/json

This error also caused by incorrect json in the body or some other problems in the body, such as unnecessary wrong invisible chars. So check generated json for errors and what is actually presents in the request body.

This error can also be raised because the body is too large.
I got it with apollo-server-micro inside a custom api route of NextJs.
It can be fixed by calling the json function coming from micro before apollo gets the request :
import { json } from 'micro'
import { ApolloServer } from 'apollo-server-micro'
const server = new ApolloServer({/*config*/})
const raiseBodyLimit: (handler: NextApiHandler) => NextApiHandler = (
handler
) => async (req, res) => {
if (req.headers['content-type'] !== 'application/json') {
return handler(req, res)
}
await json(req, { limit: '1gb' }) // This is the trick to raise body limit
return handler(req, res)
}
export default raiseBodyLimit(
server.createHandler({
path: '/api/graphql',
})
)
I saw this in this apollo-server's github issue.
Here are some information to build an apollo server endpoint with next.js

if your api upload anything you need to add the
{
uploads:true
}
in middleware while using graphql

Related

React Native Expo - Why is my proxy not working - making requests leads to TypeError: Network request failed?

Im trying to add a proxy in my react native expo app so I can make api requests to my backend without always specifying http://localhost:3001 during development.
Im not sure if its different with react native that in react apps and cannot find any info about it (everything is always about using react and roxy).
I added a proxy and when I make a request from my frontend I always get this type err:
[Unhandled promise rejection: TypeError: Network request failed]
there is nothing wrong with my code, if I type out http://localhost:3001 in my request, everything works fine. What is wrong with my proxy?
app/client/src/setupProxy.js:
const { createProxyMiddleware } = require('http-proxy-middleware');
module.exports = function(app) {
app.use(
["/api/*",],
/* '/api',*/
createProxyMiddleware({
target: 'http://localhost:3001',
changeOrigin: true,
})
);
};
app/client/components/search.js
...
const callApi = async () => {
console.log("calling the test")
const response = await fetch('http://localhost:3001/api/hello');
//that without proxy would work
const response = await fetch('/api/hello');
const body = await response.json();
if (response.status !== 200) throw Error(body.message);
console.log("body", body)
return body;
};
...
<Button title="test" onPress={callApi}>press test</Button>
...
my serverside code:
app/server/index.js
const express = require('express');
const router = express.Router();
module.exports = app => {
app.use(router.get('/api/hello', (req, res) => {
console.log("incoming req success")
res.send({ express: 'Hello From Express' });
}))
};

Sending data to Nodejs backend in Reactjs for API call

I'm trying to send data to my Nodejs server (hosted on Firebase), from Reactjs to retrieve data from an API call.
It worked when the API url was hard coded, so the issue should be in sending the data from React to Node. I'm currently trying to have it only return one request, but once I'm able to do that, I will be trying to fetch multiple requests.
The result is to populate the stocks state with the response.
My React code looks like this:
class Table extends Component {
constructor (props)
{
super(props);
this.state = {
favorites: ['APPL'],
stocks: []
};
}
componentDidMount() {
// http is adjusted for stackoverflow
fetch('http://localhost:5001/', {
// new part:
method: 'post',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
favorites: this.state.favorites})
// old part - worked before i tried to send data to the backend:
})
.then((response) => response.json())
.then(stockList => {
this.setState({ stocks: stockList });
console.log(stockList);
});
}
Node code:
const functions = require("firebase-functions");
const express = require("express");
const fetch = require("node-fetch");
const cors = require("cors");
const app = express();
app.use(cors({origin: true}));
app.get('/', async (request, response) => {
// new line that was ment to catch the data from frontend.
const favorites = request.body.favorites
// web and key is the rest of the API call.
const url = web+favorites+key;
const fetchResponse = await fetch(url);
const symbol = await fetchResponse.json();
response.json(symbol);
});
In order to get the data from the frontend you will need to make a post endpoint instead in your Node server and send a GET request inside that method.
const functions = require("firebase-functions");
const express = require("express");
const fetch = require("node-fetch");
const cors = require("cors");
const app = express();
app.use(cors({origin: true}));
app.post('/', (req, res) => {
// here is how you get the POST data from the body
console.log(req.body.favorites)
// send the data from the POST request to the API through a GET request here
})
Now you need to make a GET request where I put the comment, you can use the simpler https node.js library for that.
In my question, I'm trying to use Firebase functions to handle the link to an external API and use the data from that API in my frontend.
After some though and help from Stackoverflow, I have found that it might not be an ideal way to do it. I was basically trying to add a layer between the frontend and the API, but that is not necessary, as it is possible to reach the API directly in React. This will remove the function from Firebase, meaning less steps, less code and less fees.
So for every instance in the state.favorites, the correlated data is pulled from the API and stored in the state.stocks.
This piece of code did the trick:
class Table extends Component {
constructor (props)
{
super(props);
this.state = {
favorites: ['aapl', 'arvl'],
stocks: []
};
}
componentDidMount() {
this.state.favorites.map((favorites, index) => {
fetch(`API_website${favorites}/(API_token`)
.then((response) => response.json())
.then(stockList => {
this.setState({ stocks: stockList });
console.log(stockList);
});
})
}

Catch all routing except for /graphql

I have a node.js server application that uses express and apollo-server-express to server my application. I want to serve my react client using a catch-all routing method, however, I still want to expose the /graphql endpoint. How can I do this so that /graphql doesn't get caught in the rest of my routing? Thanks.
import express from 'express';
const app = express();
app.get('/graphql', (request, response) => {
// ? not sure what to do here.l
});
app.get('*', (request, response) => {
response.sendFile('index.html', { root: '.' });
});
You don't have to manually define the /graphql route if you are indeed using the apollo-server-express package which is recommended if you want to combine Apollo with the express middleware. The official documentation actually puts you on the right track. In your specific case, your server setup should look someting like this:
const express = require('express');
const { ApolloServer, gql } = require('apollo-server-express');
// Construct a schema, using GraphQL schema language
const typeDefs = gql`
type Query {
hello: String
}
`;
// Provide resolver functions for your schema fields
const resolvers = {
Query: {
hello: () => 'Hello world!',
},
};
const server = new ApolloServer({ typeDefs, resolvers });
const app = express();
server.applyMiddleware({ app });
app.get('*', (request, response) => {
console.log('catch-all hit.');
});
app.listen({ port: 3000 }, () =>
console.log(`🚀 Server ready at http://localhost:3000${server.graphqlPath}`)
);
Just make sure that you define your catch-all route AFTER executing server.applyMiddleware, which sets up the /graphql endpoint for you. That way the /graphql endpoint is hit first and will be used to handle those requests. All the other requests will be handled by the catch-all.

How to act as a proxy server and modify incoming requests and then forwared them in NodeJS?

I am building a proxy service that will forward all but one kind of a POST request to another server.
I was planning on using express-http-proxy to do this but I can't find a way to modify the POST request on the fly.
For Example:
I want to catch all POST requests to /getdata and check if they have a field called username,
if they do I want to replace it with a custom username and then forward the request to another server and then forward the response from it back to the user.
Any help is appreciated. Any package or resource would help. Thanks.
I was facing a similar issue recently and ended up using http-proxy-middleware with the following config (based on this recipe):
const express = require('express');
const {createProxyMiddleware} = require('http-proxy-middleware');
const bodyParser = require('body-parser');
const app = express();
app.use(bodyParser.json());
const options = {
target: '<your-target>',
changeOrigin: true,
onProxyReq: (proxyReq, req, res) => {
if (req.path === '/getdata' && req.body && req.body.userName) {
req.body.userName = "someOtherUser";
const bodyData = JSON.stringify(req.body);
proxyReq.setHeader('Content-Type', 'application/json');
proxyReq.setHeader('Content-Length', Buffer.byteLength(bodyData));
proxyReq.write(bodyData);
}
},
};
app.use(createProxyMiddleware(options));
app.listen(4001, () => console.log("listening ..."));
What did the trick for me was recalculating the Content-Length using this line:
proxyReq.setHeader('Content-Length', Buffer.byteLength(bodyData));

Getting 404 (Not Found) on axios.delete()

I'm trying to implement the delete functionality for my survey creation app.
I'm using MongoDB with mongoose for the database, node.js/Express for the backend server and React/Redux for the frontend side.
Although I think I set routing correctly, I get 404 (Not Found) on axios.delete().
The error says that http://localhost:3000/api/surveys/delete/{the-survey-id-here} is not found.
I have been reading the documentation of axios, Express, mongoose and other websites, however, nothing worked for me.
I tried the following things.
Use findByIdAndRemove() instead of deleteOne()
Pass surveyId in the action creator const response = await axios.delete("/api/surveys", data: { surveyId });
Use <a></a> instead of <button></button>
Here are my codes.
SurveyList.js (react component which has the delete button)
import { fetchSurveys, deleteSurvey } from '../../actions'
...
<div className="card-action">
<a className="red-text text-accent-1">Yes: {survey.yes}</a>
<a className="red-text text-accent-1">No: {survey.no}</a>
<button
className="btn-floating btn-small waves-effect waves-light red right"
onClick={() => this.props.deleteSurvey(survey._id)}
>
<i className="material-icons">delete_forever</i>
</button>
</div>
...
const mapStateToProps = ({ surveys }) => {
return { surveys }
}
export default connect(
mapStateToProps,
{ fetchSurveys, deleteSurvey }
)(SurveyList)
actions/index.js (action creator)
export const deleteSurvey = (surveyId) => async dispatch => {
const response = await axios.delete(`/api/surveys/delete/${surveyId}`)
dispatch({ type: FETCH_SURVEYS, payload: response.data })
}
surveyRoute.js (routing handler)
app.delete('/api/surveys/delete/:surveyId', async (req, res) => {
await Survey.deleteOne({ _id: req.params.surveyId })
const surveys = await Survey.find({ _user: req.user.id }).select({
recipients: false
})
res.send(surveys)
})
server/index.js
const express = require('express')
const mongoose = require('mongoose')
const cookieSession = require('cookie-session')
const passport = require('passport')
const bodyParser = require('body-parser')
const keys = require('./config/keys')
require('./models/User')
require('./models/Survey')
require('./services/passport')
mongoose.connect(keys.mongoURI)
const app = express()
app.use(bodyParser.json())
app.use(
cookieSession({
maxAge: 30 * 24 * 60 * 60 * 1000, // milliseconds
keys: [keys.cookieKey]
})
)
app.use(passport.initialize())
app.use(passport.session())
require('./routes/authRoutes')(app)
require('./routes/billingRoutes')(app)
require('./routes/surveyRoutes')(app)
if(process.env.NODE_ENV === 'production'){
app.use(express.static('client/build'))
const path = require('path')
app.get('*',(req, res) => {
res.sendFile(path.resolve(__dirname, 'client', 'build', 'index.html'));
})
}
const PORT = process.env.PORT || 5000
app.listen(PORT)
In my case, leads/ is the end point. try your end point + survey id
With axios#^0.19.2
axios
.delete('/api/leads/' + id)
.then(res => {
dispatch({
type: DELETE_LEAD,
payload: id
});
So if I undestood correctly, you have a Expressjs App and a client app running on different ports, if so I think I found the issue
You are making ajax calls as if you had both frontend and backend running on the same server. Here are two possible solutions
Change the address of the Axios delete action to point to the right server if you are planning to run the backend and frontend on different servers
app.delete('http://localhost:5000/api/surveys/delete/:surveyId', async (req, res) => {
await Survey.deleteOne({ _id: req.params.surveyId })
const surveys = await Survey.find({ _user: req.user.id }).select({
recipients: false
})
res.send(surveys)
})
Off course you have to change the address once you deploy it to production and enable CORS
Build the client app for producion, this process will compile all css, js and html and save it to client/build folder as stated on line
res.sendFile(path.resolve(__dirname, 'client', 'build', 'index.html'));
This will cause the frontend and backend code to run on the same server
Thank you very much for all answers and comments. The problem was the proxy. I am using http-proxy-middleware to handle requests between different ports in the dev environment. All comments about ports made me realize that I need to check the 'setupProxy.js' file. I modified the code inside the file as below and that fixed the problem.
setupProxy.js
const proxy = require('http-proxy-middleware')
module.exports = function(app) {
app.use(proxy ('/auth/google', { target: 'http://localhost:5000' }))
app.use(proxy ('/api/**', { target: 'http://localhost:5000' }))
}
Thank you very much again for all the answers and comments.
CORS (Delete verb) must be enabled in your route middleware.
This thread may help you.

Resources