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.
Related
I am trying to get function result from backend to frontend via axios but it returns 404 every time.
I managed to send request to backend and activate function but on geting result it started returning 404
route in app.ts
import cardRoute from './routes/test';
const app = express();
app.use('/test', cardRoute);
./routes/test.ts (backend)
function test_load() returns string
import express from 'express';
import { test_load } from '../cardpull';
const router = express.Router();
router.post('./test-bed',
async (req, res) => {
let cards = test_load()
res.send(cards);
},
);
export default router;
Frontend call
async function GetCard() {
var cards = await axios.post<string>('/test/test-bed');
return cards;
};
your route is not valid
router.post('./test-bed',
async (req, res) => {
let cards = test_load()
res.send(cards);
},
);
should be:
router.post('/test-bed',
async (req, res) => {
let cards = test_load()
res.send(cards);
},
);
and on your axios URL, maybe you need to include the host and port because if you define only the endpoint, it will hit your frontend host and port,
example if you open express on localhost:3000
then the axios will be
axios.post('http://localhost:3000/test/test-bed')
note: I didn't write the answer with typescript, but with javascript style should be clear enough.
I'm having trouble with the error message in the title when trying to retrieve all users in my express .get('/users') method. I am using Node.js, Express, and node-postgres. I have my
getUsers(); function defined in my queries.js file, and I call the function in my app.get() function in my index.js file.
queries.js
const client = require('./object models/db_client_pool')
const Pool = require('pg').Pool
const pool = new Pool(client.client)
async function getUsers(request, response) {
await pool.connect()
pool.query('select * from discord_users', (error, results) => {
if (error) {
throw error
}
response.sendStatus(200).json(results.rows)
pool.release();
})
}
module.exports = {
getUsers
}
index.js
const express = require('express');
require('dotenv').config();
//const bodyParser = require('body-parser'); deprecated
const app = express();
const port = 3000;
const db = require('./queries');
app.use(express.json())
app.use(express.urlencoded({
extended: true
}))
app.get('/', (request, response) => {
response.json({ info: 'Node.js, Express, and Postgres API' })
})
app.get('/users', (req, res) => {
db.getUsers(req, res)
})
app.listen(port, () => {
console.log(`App is listening on port ${port}`);
});
As I said, I keep getting the "cannot set headers after they are sent to the client" error and I'm at a loss of what to do. Thanks in advance for your help!
Change from this:
response.sendStatus(200).json(results.rows)
to this:
response.status(200).json(results.rows);
or even just to this:
response.json(result.rows); // 200 is the default status already
The last one is fine because 200 is already the default status so you don't need to set that yourself.
The problem is that response.sendStatus(200) sends a complete response with an empty body and then you try to call response.json(result.rows) which tries to send ANOTHER response to the same request. Trying to send that second response to the same request is what triggers the error message you are getting.
response.status(200) just sets the status to 200 as a property on the waiting response object and waits for some other method to actually send the response itself which you can then do with .json(...).
So my guess is, you're running express 4.x and that doesn't support response.sendStatus(200) anymore. You have to use response.status(200) instead.
Now, another issue I see in your code is, I don't recognize pool.release() method from pg library. You can release a client back to a pool but you can't release a pool of clients. Maybe you meant pool.end()?
I am having trouble wrapping my head around how to use the /:variable notation effectively. In my setup I have this layout.
router.route("/").get()
router.route("/:id").get().put().post().delete()
router.route("/auth").get().put().post()
When I call /auth it fails to go there, instead it triggers the method under the /:id. How can I make sure when I say /auth it does not goto that /:id path.
You need to adjust the order of route definition. Move /auth route to /:id before route.
E.g.
import express, { Router } from 'express';
const app = express();
const port = 3000;
const router = Router();
router.route('/auth').get((req, res) => {
res.send('auth');
});
router.route('/:id').get((req, res) => {
res.send({ id: req.params.id });
});
app.use('/user', router);
app.listen(port, () => console.log(`HTTP server is listening on http://localhost:${port}`));
Test and output:
âš¡ curl http://localhost:3000/user/auth
auth%
âš¡ curl http://localhost:3000/user/123
{"id":"123"}%
Req.body is not accessible in the routes while making a post request. It would be highly appreciative of someone if he/she help me getting through it. Here is screenshot of my microservice.test.js file. Am I missing something?
import request from "supertest";
import mongoose from "mongoose";
import config from "../config/env";
import routes from "../server/routes";
import { parseResponse, doRequest } from "../server/utils/helperFunctions";
const app = express();
app.use("/", routes);
jest.setTimeout(30000);
The code provided doesn't provide much insight, as I would expect all of the handling of the request to be in your route handler. Is the issue that you are unable to access the body when running tests with supertest? Or that it isn't working at all. More information would be very helpful.
If it is a supertest issue, I would recommend checking out the docs for good examples. Here is one I pulled directly from the NPM site where they POST some data with a request body and then verify the response body:
describe('POST /user', function() {
it('user.name should be an case-insensitive match for "john"', function(done) {
request(app)
.post('/user')
.send('name=john') // x-www-form-urlencoded upload
.set('Accept', 'application/json')
.expect(function(res) {
res.body.id = 'some fixed id';
res.body.name = res.body.name.toLowerCase();
})
.expect(200, {
id: 'some fixed id',
name: 'john'
}, done);
});
});
Also, if you are trying to test your server you should probably import your server code instead of creating a new express instance. For example, in your server code you'll have something like this:
server.js
const express = require('express');
const app = express();
app.use('/', ...) // middleware/route config
...
module.exports = app;
Your server would then use this server like this:
index.js
const app = require('./server')
const port = 4000
app.listen({ port }, () => {
const location = `http://localhost:${port}`
logger.info(`🚀 Server ready at ${location}`)
})
module.exports = app
Now that you have structured your code this way, in your test you can import your server as well (so you are testing your actual server, not a new server that you made up):
server.test.js
const app = require('../../../server');
const request = require('supertest')(app);
describe('Server', () => {
it('does a thing', async () => {
const { body } = await request
.post('http://path/to/test')
.send({ data: 'some data' })
.set('Content-Type', 'application/json')
.set('Accept', 'application/json')
.expect(200);
expect(body.thing).toBeTrue();
});
});
expressjs version lower than 4 include body parsing middleware
import bodyParser from 'body-parser';
app.use(bodyParser());
example test
it('.post should work with data', function (done) {
var app = express();
app.use(bodyParser());
app.post('/', function(req, res){
res.send(req.body.name);
});
request(app)
.post('/')
.send({ name: 'tobi' })
.expect('tobi', done);
})
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