In questo tutorial vedremo come effettuare delle ricerche per data su MongoDB in Node.js utilizzando Mongoose.
Il caso d'uso più semplice è quello della ricerca per data esatta, ossia inserendo anno, mese e giorno. Bisogna tenere presente che i parametri di ricerca che andranno a comporre l'oggetto Date
finale dovranno essere validati.
Un giorno è ovviamente composto di 24 ore, quindi per comprendere l'intero arco temporale dovremo usare due date, la prima che ha l'orario impostato su 00:00:00
e la seconda con l'orario impostato su 23:59:59
, entrambe con il medesimo anno, mese e giorno.
Le due date dovranno essere gestite usando i metodi dell'oggetto Date
che utilizzano il formato UTC. Se non lo facessimo, riscontreremmo delle discrepanze dovute all'ambiente locale in cui viene eseguito Node.js.
Mongoose ci fornisce gli operatori $gte
(greater-than-equal) e $lte
(less-than-equal) che operano sulle date sia quando i valori sono passati come stringa che quando sono istanze dell'oggetto Date
. Utilizzandoli possiamo reperire quei documenti il cui campo di tipo ISODate
ricade nell'intervallo temporale specificato.
Per prima cosa definiamo due metodi helper di validazione dei parametri passati alle API di ExpressJS.
module.exports = {
validYear(str) {
if(!/^2\d{3}$/.test(str)) {
return false;
}
const year = new Date().getFullYear();
const min = year - 4; // Limite minimo
const value = parseInt(str, 10);
return (value >= min && value <= year);
},
validValue(str, type = 'month') {
if(!/^\d{1,2}$/.test(str)) {
return false;
}
const value = parseInt(str, 10);
let valid = true;
switch (type) {
case 'month':
if(value < 0 || value > 11) {
valid = false;
}
break;
case 'day':
if(value < 1 || value > 31) {
valid = false;
}
break;
default:
break;
}
return valid;
}
}
A questo punto possiamo definire la route delle API che effettuerà la ricerca per data.
'use strict';
const express = require('express');
const router = express.Router();
const stats = require('../models/stats');
const utils = require('../lib/utils');
router.post('/search', async (req, res, next) => {
const body = req.body;
if(typeof body.year === 'undefined' || typeof body.month === 'undefined' || typeof body.day === 'undefined') {
return res.json({
error: 'Missing required parameters.'
});
}
const { year, month, day } = body;
if(!utils.validYear(year)) {
return res.json({
error: 'Invalid year parameter.'
});
}
if(!utils.validValue(month, 'month')) {
return res.json({
error: 'Invalid month parameter.'
});
}
if(!utils.validValue(day, 'day')) {
return res.json({
error: 'Invalid day parameter.'
});
}
const dateStart = new Date();
dateStart.setUTCFullYear(parseInt(year, 10));
dateStart.setUTCMonth(parseInt(month, 10));
dateStart.setUTCDate(parseInt(day, 10));
dateStart.setUTCHours(0, 0, 0);
const dateMax = new Date();
dateMax.setUTCFullYear(parseInt(year, 10));
dateMax.setUTCMonth(parseInt(month, 10));
dateMax.setUTCDate(parseInt(day, 10));
dateMax.setUTCHours(23, 59, 59);
try {
const query = { date: {
$gte: dateStart, $lte: dateMax
} };
const results = await stats.find(query);
res.json(results);
} catch (err) {
res.json(err);
}
});
module.exports = router;
Dopo aver effettuato la validazione dei tre parametri della richiesta POST, creiamo i due intervalli temporali usando appunto tali parametri. Al metodo find()
della collezione scelta viene passato un oggetto che definisce la nostra query al database.
Il punto cruciale per ottenere risultati coerenti è l'impiego del formato UTC per le date. Così facendo, siamo certi che non verrà usata la configurazione locale di Node.js per effettuare tali calcoli.