Express routes alongside socket.io ones - node.js

I'm trying to handle regular http requests alongside socket.io.
The app.ts file has some REST Route handles, and error handler for all routes that don't exist.
app.use("/users", userRouter);
app.use("/products", productRouter);
app.all("*", async (req: Request, res: Response) => {
throw new NotFoundError("route");
});
Suppose I want to add one route (namespace) that will handle the socket.io connections.
With my current implementation I keep getting an error that the route doesn't exist (from the app.all) because express thinks it's a regular http client.
What is the right way to implement regular http requests alongside socket.io ones?
I know that in order to implement namespace I need to use
const io = new Server(httpServer, { cors: { origin: "*" } });
io.of("/socket").on("connection", () => {
// Do something
});
But localhost::PORT/socket is treated as if it is a regular request

Related

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.

Express server for both serving pages and endpoint

I want to have a Node web server serving pages and also set as an endpoint listening to webhooks. The example for the first comes from Rocket Rides, with the relevant code being:
const express = require('express');
// ...
const app = express();
// ...
// CRUD routes for the pilot signup and dashboard
app.use('/pilots', require('./routes/pilots/pilots'));
app.use('/pilots/stripe', require('./routes/pilots/stripe'));
// ...
// Index page for Rocket Rides
app.get('/', (req, res) => {
res.render('index');
});
// ...
// Start the server on the correct port
const server = app.listen(process.env.PORT || config.port, () => {
console.log('🚀 Rocket Rides server started:', config.publicDomain);
});
For the second, I use this tutorial with the following relevant code:
// Match the raw body to content type application/json
app.post('/webhook', bodyParser.raw({type: 'application/json'}), (request, response) => {
console.log("called!");
let event;
try {
event = JSON.parse(request.body);
} catch (err) {
response.status(400).send(`Webhook Error: ${err.message}`);
}
// Handle the event
switch (event.type) {
case 'payment_intent.succeeded':
const paymentIntentSucceeded = event.data.object;
break;
case 'payment_method.attached':
const paymentMethod = event.data.object;
break;
// ... handle other event types
default:
// Unexpected event type
return response.status(400).end();
}
// Return a response to acknowledge receipt of the event
response.json({received: true});
});
app.listen(8000, () => console.log('Webhooks running on port 8000'));
With both parts, the server does not handle the webhook request:
Webhooks running on port 8000
POST /webhook 404 590.525 ms - 1415
and the sender receives a 404.
When I comment out most of the code in the first part, the webhook request is handled properly:
Webhooks running on port 8000
called!
and the sender receives a 200.
I believe one of the routes from the web server is masking the route for the endpoint. I tried looking for one with this thread:
app._router.stack.forEach(function(middleware){
if(middleware.route){ // routes registered directly on the app
routes.push(middleware.route);
} else if(middleware.name === 'router'){ // router middleware
middleware.handle.stack.forEach(function(handler){
route = handler.route;
route && routes.push(route);
});
}
});
console.log(routes);
and the only relevant one was GET /.
If I include the code for the endpoint before the code for the router, the webhook is handled properly.
How can I find which route is masking the webhook endpoint?
Put the more specific route definitions first like this:
app.use('/pilots/stripe', require('./routes/pilots/stripe'));
app.use('/pilots', require('./routes/pilots/pilots'));
And, the more general route definitions later. That makes sure the more specific routes aren't gobbled up by the more general handlers.
Keep in mind that with app.use(), something like app.use('/pilots') will match any route that starts with /pilots which would include all your /pilots/stripe routes. So, you want to make sure and put the app.use('/pilots/stripe', ...) before the app.use('/pilots', ...).
Another observation. In your /webhook handler, you need to return after you send an error status so the rest of your request handler doesn't continue to run.
// Match the raw body to content type application/json
app.post('/webhook', bodyParser.raw({type: 'application/json'}), (request, response) => {
console.log("called!");
let event;
try {
event = JSON.parse(request.body);
} catch (err) {
response.status(400).send(`Webhook Error: ${err.message}`);
return; // <===== Add this
}
....
}
This appears to be a bug in the actual stripe documentation.
If I include the code for the endpoint before the code for the router, the webhook is handled properly.
I would guess that you have bodyparser middleware elsewhere in your server. If that middleware is BEFORE this route, then this route won't get to use its bodyParser.raw() and get the data the way it wants and it will not work properly. This is because whichever bodyParser middleware runs first reads the body and parses it and puts it wherever that middleware is configured to put it. Once the body is read, it's gone from the stream so any other middleware that comes along and also tries to read the body data from the stream will find the stream empty.
So, this route just has to be before any other body parsing middleware that might handle JSON.
If you provided a link to your full code, we could take a look and see where this is happening.

CSRF-protection for routes of proxied requests in express.js

I'm using http-proxy-middleware (and open to suggestions, but would like to stick to the modules which are proxying requests instead of creating new ones like request or http) to proxy requests to remote host.
Two problems I'm not seeing solutions to currenty:
1) I have a CSRF-protected form (via csurf). I would like it that the middleware checks the CSRF-token first, and in case it is valid only then proxies the request to another host, obtains the response and sends it to user. How to achieve such a setup?
2) http-proxy-middleware (and some other proxying modules) utilizes app.use to set one forwarding rule (append the route to the host), however I would like to have a more fine-grained control over routes - each of my routes must have its own endpoint on the remote host.
The code:
const express = require('express')
const csrf = require('csurf')
const cookieParser = require('cookie-parser')
const proxy = require('http-proxy-middleware')
var app = express()
var csrfProtection = csrf({ cookie: true })
app.use(cookieParser())
// not quite what I need, since different
// routes would utilize different endpoints
app.use('/api', proxy('http://example.com'))
app.get('/forms', (req, res) => {
res.send(
res.render('csrf-protected-forms.html', { csrfToken: req.csrfToken() })
)
})
app.post('/api/one', csrfProtection, (req, res) => {
response = // proxies to 'http://example.com/something/one'
// and obtains response
res.send(response)
})
app.post('/api/two', csrfProtection, (req, res) => {
response = // proxies to 'http://example.com/somethingelse/and/here/two'
// and obtains response
res.send(response)
})
app.listen(3000)
In your code csrf protection runs after proxied middleware. In case if you want protect only this two routes '/api/one','/api/two':
app.use(['/api/one','/api/two'], csrfProtection, proxy('http://example.com'))
app.use('/api', proxy('http://example.com'))
Or if you want protect all POST requests to API, you need somthing this:
app.use('/api', csrfProtection, proxy('http://example.com'))

Cannot determine why request-promise-native fails on a 'POST with a 404'

I'm trying to make this brief. Hopefully, it isn't so brief it makes no sense. I need to read a list of identifiers from an API that will be used to subsequently 'GET' the JSON files associated with the keys in the list. The list array is stored in another JSON file at the same endpoint with an identifier of 'keys.json' In order to prevent the list from being processed twice, I want to immediately write an empty array back to 'keys.json' upon retrieving it.
Here is the successful function that 'GETS' the list.
const getJsonKeys = async () => {
const options = {
method: 'GET',
uri: baseURL + keysID,
headers: { 'User-Agent': 'Request-Promise' },
json: true // Automatically parses the JSON string in the response
};
return (await rpn(options)).keys;
};
Here is the unsuccessful 'POST' that I try to write with:
const postEmptyJsonKeys = async () => {
const options = {
method: 'POST',
uri: baseURL + keysID,
body: {
keys: []
},
json: true // Automatically stringifies the body to JSON
};
return (await rpn(options)).req.Request.body;
};
Here is the block that calls them both:
module.exports = (rtProcess) => {
rtProcess.get('/process', async (req, res, next) => {
const jsonKeysList = await (getJsonKeys());
console.log("Retrieved Keys", jsonKeysList);
try {
const req = await (postEmptyJsonKeys());
console.log("Wrote", req.Request.body);
} catch(err) {
console.log(err.statusCode, err.error);
console.log(err);
}
//
// more code here
//
jsonKeysList.forEach(courseID => {
//
// more code here
//
});
res.render("process");
}); // end of process route
}; // end of module exports
I have tried everything I know to do to ferret out the answer in the various docs around but I can find nothing that tells me why the catch block is taken, rather than getting a successful try.
BTW the error.status code is a 404.
The error is a string that looks like HTML, which is also a mystery to me since I am trying to POST a simple:
{
keys: []
}
So this error:
Cannot POST /static/quizdata/keys.json
Makes me think the API endpoint you are POSTing to is not properly defined, and that's why you get a 404 It's telling you there is no POST handler that matches that request.
As the question has the node.js tag and I can see the static part in the URL, that makes me think you might be serving that static content with express.static built-in middleware, and if that's the case, then that's why you can't POST anything there, as that middleware is not meant for that and will only take care of GET requests.
Regarding your comment, it's not static content because the route has static in it or because the content it's in a directory called static (if that's the case).
Take a look at this examples:
This will handle GET requests like http.../static/path/inside/public/dir.png:
app.use('/static', express.static('public'));
This will handle GET requests like http.../assets/path/inside/public/dir.png:
app.use('/assets', express.static('public'));
This will handle GET requests like http.../whatever/something/inside/public/dir.png:
app.use('/whatever', express.static('public'));
This will handle GET requests like http.../something/inside/public/dir.png:
app.use(express.static('public'));
This will handle GET requests like http.../something/inside/my-static-files/dir.png:
app.use(express.static('my-static-files'));
You can find more examples in the official docs.
In any case, let's assume you are serving static content using this option:
app.use('/static', express.static('public'));
You can still add another middleware to handle POST requests. Something like this:
const express = require('express');
const fs = require('fs');
const app = express();
const port = 3000;
...
app.post('/static', (req, res, next) => {
// Just an example, you can replace the path and the []
// with anything you want:
fs.writeFileSync('path/to/file.json', JSON.stringify([]));
});
app.use('/static', express.static('public'));
app.listen(port, () => console.log(`Listening on port ${port}!`));
You might also find this other question useful: Appending a JSON file from JS file in Express Application

Cannot GET / error using express

I'm new to nodeJS, I'm trying to follow this tutorial.
My code:
// server/index.js
import express from 'express';
import { graphqlExpress, graphiqlExpress } from 'graphql-server-express';
import { makeExecutableSchema, addMockFunctionsToSchema } from 'graphql-tools';
import bodyParser from 'body-parser';
import { createServer } from 'http';
import { Schema } from './data/schema';
import { Mocks } from './data/mocks';
const GRAPHQL_PORT = 8000;
const app = express();
const executableSchema = makeExecutableSchema({
typeDefs: Schema,
});
addMockFunctionsToSchema({
schema: executableSchema,
mocks: Mocks,
preserveResolvers: true,
});
// `context` must be an object and can't be undefined when using connectors
app.use('/graphql', bodyParser.json(), graphqlExpress({
schema: executableSchema,
context: {}, // at least(!) an empty object
}));
app.use('/graphiql', graphiqlExpress({
endpointURL: '/graphql',
}));
const graphQLServer = createServer(app);
graphQLServer.listen(GRAPHQL_PORT, () => console.log(`GraphQL Server is now running on http://localhost:${GRAPHQL_PORT}/graphql`));
reports an error Cannot GET /
I've read that maybe the createServer function is deprecated, but I'm not sure how to fix it.
When you use express, you have to be explicitly define the routes used by your application. For example, if you define a route with app.get('/hello', handler) then any GET requests to localhost/hello will get routed to that handler. It can then execute whatever logic and return a response, be that a JSON object, a webpage, etc.
Express will only handle the routes you've defined in this way. So if you've only defined a route for requests that GET /hello, it will not know how to GET /foo, or GET your root path /. If you wanted to implement a way to POST or PUT to /hello, that would need to be a different route as well.
You can use app.use in a similar way to implement middleware in your application. While middleware typically takes your request, manipulates it and pass it on, it can also be used to break up your routing logic.
In the case of GraphQL, requests are typically made using the POST method, but the specification does allow for both POST and GET requests. To do this, we would have to define handlers for both app.get('/graphql') and app.post('/graphql'). The graphqlExpress middleware you're importing and using conveniently does that for you.
So with your set up, you've created some routes that allow you to POST to and GET from localhost:8000/graphql. You've also enabled GraphiQL on localhost:8000/graphiql. If you don't see any errors in the console when you start your server, you should be able to navigate to the GraphiQL page at localhost:8000/graphiql and play around with your schema.
But these are the only routes you've set up on your server. If you attempt to navigate anywhere else, like the root at localhost:8000/, express won't know how to handle that request and you will see the error you reported.
Use app.listen(port, function(){console.log('server started')}); instead of createServer(app).

Resources