Parse sendBeacon data in Express - javascript

I'm trying to start logging my own WebVitals. The simple use-case in their example docs looks like this:
function sendToAnalytics(metric) {
// Replace with whatever serialization method you prefer.
// Note: JSON.stringify will likely include more data than you need.
const body = JSON.stringify(metric);
// Use `navigator.sendBeacon()` if available, falling back to `fetch()`.
(navigator.sendBeacon && navigator.sendBeacon('/analytics', body)) ||
fetch('/analytics', {body, method: 'POST', keepalive: true});
}
That all seems simple enough. Here's my actual implementation:
function sendToLog(metric) {
// Replace with whatever serialization method you prefer.
// Note: JSON.stringify will likely include more data than you need.
const body = JSON.stringify(metric);
console.log(`Sending body ${body}`);
// Use `navigator.sendBeacon()` if available, falling back to `fetch()`.
// (navigator.sendBeacon && navigator.sendBeacon('https://localho st:9292/api/log', body)) ||
fetch('https://localhost:9292/api/log', {body: body, method: 'POST', headers: { 'Content-Type': 'application/json' }, keepalive: true});
}
I had to modify the fetch to include the "body" property name and add headers to get it to work, but that is now working. However, if I uncomment the navigator.sendBeacon line, I just get {} for the body.
I'm using NodeJS and Express on the backend. My starting point was this:
const app = express();
app.use(
express.json({
// We need the raw body to verify webhook signatures.
// Let's compute it only when hitting the Stripe webhook endpoint.
verify: function (req, res, buf) {
if (req.originalUrl.startsWith(API_PREFIX + '/webhook')) {
req.rawBody = buf.toString();
}
},
})
);
app.use(cors());
// Expose an endpoint for client logging.
app.post(API_PREFIX + '/log', async (req, res) => {
const message = req.body;
console.log('Got message');
console.dir(message, { depth: null });
logger.info(message);
res.sendStatus(200);
});
There's this similar question, where the accepted answer suggests that adding body-parser and app.use(bodyParser.raw()) should do the trick, and then in the comments there's discussion of using bodyParser.json()) instead.
I've tried both of those:
import bodyParser from 'body-parser';
...
//app.use(bodyParser.raw());
//app.use(bodyParser.json());
app.use(
express.json({
// We need the raw body to verify webhook signatures.
// Let's compute it only when hitting the Stripe webhook endpoint.
verify: function (req, res, buf) {
if (req.originalUrl.startsWith(API_PREFIX + '/webhook')) {
req.rawBody = buf.toString();
}
},
})
);
app.use(cors());
(i.e., uncommenting either of the two lines at the beginning), and in all 3 cases I still get an empty body when sendBeacon is used.
The Express documentation says,
[express.json] is a built-in middleware function in Express. It
parses incoming requests with JSON payloads and is based on
body-parser....A new body object containing the parsed data is
populated on the request object after the middleware (i.e. req.body),
or an empty object ({}) if there was no body to parse, the
Content-Type was not matched, or an error occurred.
So, a) I guess I shouldn't need the body-parser, since express.json is just doing that anyhow; and b) I'm hitting one of those three conditions (there's no body to parse, the Content-Type was not matched, or an error occurred). Assuming that's the case, how do I diagnose which one it is, and then fix it (in such a way that the fetch fallback continues to work). Or, if there's some other problem, back to the original question, how do I get this to work? :)

navigator.sendBeacon sends Content-Type: text/plain whereas express.json() only parses the body if Content-Type: application/json.
Use express.text() in combination with JSON.parse(req.body) instead, or additionally:
app.post(API_PREFIX + '/log',
express.json(), express.text(), function(req, res) {
if (typeof req.body === "string") req.body = JSON.parse(req.body);
...
});

Related

FormData() append - Receive only empty objects

I started a new project and had never issues to append data to FormData and sending it to the backend. Currently, I receive only empty objects in the backend. What am I missing here?
this.first_name is not empty if I console.log it. That's not the problem here.
async createAgent() {
const data = new FormData()
data.append('first_name', this.first_name)
// data.append('last_name', this.last_name)
// data.append('phone', this.phone)
// data.append('email', this.email)
// data.append('languages', this.checkedLanguage)
// data.append('image', this.selectedFile)
try {
const post = await this.$axios.$post('/api/create-agent', data)
}
Node.js
exports.createAgent = async (req, res) => {
console.log(req.body.first_name);
console.log(req.body);
};
Console Output
undefined
{}
index.js (Node.js)
const app = express();
app.use(morgan("dev"));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
According to body-parser's docs:
This does not handle multipart bodies, due to their complex and typically large nature.
which in your screenshot, on the Request Headers part, is stated.
Solutions in your example are:
just send a JSON payload (parsed by bodyParser.json())
set Content-Type to application/x-www-form-urlencoded (parsed by bodyParser.urlencoded())
use the recommended modules for multipart body in the docs:
Docs: https://github.com/expressjs/body-parser/tree/1.20.0#readme
To send a JSON payload with axios which you are using in the OP, do:
axios.post({
first_name: "This is a JSON property"
})
or
$axios.$post()
which IF you're using nuxt/axios, is a convenient function that will return the response body instead of the whole response.
Docs: https://axios.nuxtjs.org/usage#-shortcuts

Parsing Body from request to Class in express

I got doubt, im coming from .net with c# and i want to parse my body request as .net does for my automatically, how can i set a class or an interface as a request body in express, i have found many options but all of them just destruct the body into the properties that they need, i need a way or a method that allows me to get only the properties that i specified in my class.
In .Net it will be something like this.
[HttpGet("someName")
Public IActionResult GetSomething(Myclass instance){
// the instance with only the properties that i specified in my class and not the hole body with useless props that i don’t request
instance.someProperty
Return Ok()
}
ASP.net is actually smart enough to understand that when a class is declared as an argument, it must map from the request POST body to the class.
Nodejs and express do not always come with batteries included.
You need to add a middleware that can read the raw request and get the json object you want. If you are only receiving JSON then you need the JSON middleware. If you expect to have URL encoded posts (for file uploading or html s) then you also need to add the urlencoded middleware
const app: Application = express();
(...)
app.use(express.json());
app.use(express.urlencoded());
At this point, you can declare your route, and express will corectly fill the req.body object with your post data.
interface MyPostBody {
foo: string;
bar: string;
}
app.post("/api/someName", (req, res) => {
const instance = req.body as MyPostBody;
console.log(instance.foo);
console.log(instance.bar);
const result = doSomething(instance);
res.send(result);
});
Please be aware that we are just casting the type here, so if your client sends an object that does not conform to the MyPostBody interface, things will break. You probably need to add some validation to ensure the data conforms to you api contract. You can use some validation library like yup for that. To keep it simple I will do something very basic here.
app.post("/api/someName", (req, res) => {
if(req.body.foo === null || req.body.foo === undefined) {
res.status(400).send("foo is required");
return;
}
if(req.body.bar === null || req.body.bar === undefined) {
res.status(400).send("bar is required");
return;
}
const instance = req.body as MyPostBody;
console.log(instance.foo);
console.log(instance.bar);
const result = doSomething(instance);
res.send(result);
});

Get data from HTTP GET request

I'm sending an HTTP GET request from the client to my server. On the server, I can't access the data that is passed with the GET request. It works for POST requests but the value is not received in the get request.
The client is Vue.js, and the Server is Express.js in Node.js. The code looks like this.
Client:
var response = await axios.get('/endpoint',{ key: 'value' });
Server:
router.get('/endpoint', async (req,res) => {
console.log(req.body); // empty
console.log(req.query); // empty
console.log(req.params); // empty
});
I've set up my body-parser above. It looks like below
const bodyparser = require('body-parser');
app.use( bodyparser.json() );
How do I access the {key: 'value'} object that is sent from the client in my server.
GET requests do not have bodies
With Axios you can path query params to GET call in 2 different ways:
By putting them into the endpoint path
await axios.get('/endpoint?key=value&key2=value2');
Add them to the config argument in params property object as key-value
await axios.get('/endpoint', { params: {key: 'value', key2: 'value2' }});
Your confusion absolutely makes sense, you try to use same way to pass param for get and post methods, but in Axios these methods have different number of arguments: post(url, data, config), get(url, config)
Let's return to your example:
var response = await axios.get('/endpoint',{ key: 'value' });
So now you can see that { key: 'value' } object that you pass as a second argument is request config and isn't a data.
You can learn more about request config params here - https://github.com/axios/axios#request-config
I believe the problem is that you want to access body by
console.log('req.body');
instead of
console.log(req.body);
Also, as pointed above, GET doesn't have body.

Headers not set node.js api push notification

Hello im trying to set up push notifications for my webapp.
I'm getting my subscription like I should.
It saves it to my database correctly.
It sends my notification like it should if there only is ONE user in the db
and i want to send to more than only one user :)
Im using:
Vue.js (framework)
Axios (post)
node.js (api)
mongoDB (database)
Here's my post to API.
await axios({
method: 'post',
url: 'API',
data: {
subscription: JSON.stringify(subscription),
storeId: storeId
},
headers: {
'Content-Type': 'application/json'
}
})
It registreres my post, but then i get an throw error.
that "Can't set headers after they are sent."
I'm using CORS in my app like this:
const cors = require('cors')
const app = express();
app.use(bodyParser.json());
app.use(cors())
app.use(morgan('combined'))
The way I'm handling the post from my website is by finding my subscriptions and then map through and say foreach subscription
webpush
//subscribe routes
app.post('/pushNotification', (req, res) => {
var storeId = req.body.storeId
res.setHeader('Content-Type', 'application/json');
console.log(storeId)
if (req.body.storeId != null) {
console.log('Test1')
//get pushSubscription object
//create payload
const payload = JSON.stringify({ title: 'push test' })
Push.find({
"storeId": storeId
},
'subscription', function(error, response) {
console.log('Test2')
console.log(response)
response.map(item => {
res.status(201).json({});
console.log('Test3')
var subscription = item.subscription
console.log(subscription)
webpush.sendNotification(subscription, payload).catch(err => console.error(err));
})
})
} else {
res.send("failed")
}
})
As i can read around somewhere is it im not setting headers or something right. I have used cors like in tutorials and stuff.
So it's like it is crashing because it iterates wrong.
but i can't see how.
ERROR MESSAGE:
Thanks in advance
you are getting this error because res.status(201).json({}) has already set the headers and sent back the response to the client but webpush.sendNotification also trying to set the headers.You should use only webpush.sendNotification(subscription, payload).catch(err => console.error(err));
res.json([body]) sets the corresponding header and sends the result:
Sends a JSON response. This method sends a response (with the correct content-type) that is the parameter converted to a JSON string using JSON.stringify().
So, first of all you don't need to set header manually.
second, If the response has more than one item, since you can't send multiple result for a request, you shouldn't use res.json in a map.
Moreover, be aware of webpush.sendNotification that it may send a result too.

Neither Node.js PUT or POST routes are presenting the data that is being received

I tried this with a number of different modules and methods.
Even by proving the other modules do work as expected in Node by building a separate test project and testing each module individually.
Posting FROM Node's hosted router to a remote API (not TO Node's hosted API)
This problem is not one of SENDING data to an API. It must IMO a problem in the receiving API's not giving up the data it IS receiving for some reason.
I've proven the PUT or POST calls are sending the data by sending the call to http://httpbin/org. That site shows me I'm sending what I expect to be sending.
Here is how I'm sending. I can even see in the receiving API that that API is certainly getting called successfully.
-- sending -- ((Again. This shows my node.http attempt. But I get the same problem using requestjs, requestifyjs, needlejs))
router.get('/', function (req, res, next) {
var hst = req.headers.host.split(':');
var lookbackURL = 'https://' + req.headers.host + req.baseUrl;
lookbackURL = 'http"httpbin.org/put';
var dat = {
what: 'ever'
, try: 'again'
};
var bdy = JSON.stringify(dat);
var options = {
host: hst[0], port: hst[1], path: req.baseUrl, method: 'PUT'
, headers: { 'Content-Type': 'application/json' }
};
var r = nodeHttp.request(options); r.write(bdy); r.end();
res.sendStatus(200);
});
-- receiving --
router.put('/', function (req, res, next) {
console.log('r', req);
});
No matter what module or method I use, in all cases, the receiving req object doesn't contain the what or try data.
BUT in my test project the data is there as I expect it to be, in all cases.
Doing the same console.log(req); in the test project, reqestjs, requestjs, needlejs, node.http all show a proper body object.
But in this problem there isn't a body object in req.
And sending this put/post to http://httpbin.org I can see the body object is being sent.
Any ideas?
Issue found. And it was something no one on here could have gotten for the code I posted.
For reasons I will not go into I have to take body-parser out this application. This also means app.use() won't have a parser given to it.
And that means I have to deal with getting the data on my own. So I've added a req.on('data') listener to read the chunk from the call to the API.
router.put('/', function (req, res, next) {
var data = '';
req.on('data', function (chunk) {
data += chunk;
.....
});
.....
I also decided to do this as a PUT using Requestify.
This just goes to show how easy it is to become complacent and forget how things really work; the assumption of body-parser (or other things) always being there for instance. Or what it is really doing for you.
NEXT I have to figure out how to get a value out of the `req.on('data) back to the method PUTting to the API. Any tips? Appreciated.

Categories

Resources