How to increment property in MongoDB collection using $inc - javascript

I have a list of articles that have a property views and I want to increment that property in the database each time a user clicks on an article title. Currently nothing happens when I do it. Why isn't it working and how can I increment that property each time on click? Here is my React part:
const incrementViews = (id) => {
var item = posts.find(x => x._id === id);
item.views += 1;
}
<div className="post-title">
<Link to={`/post/${post._id}`}>
<h2><a href="#" onClick={() => incrementViews(post._id)}>{post.title}</a>
</h2>
</Link>
</div>
and my server.js:
// Requiring the dependencies
const express = require('express');
const app = express();
const bodyParser = require('body-parser');
const cors = require('cors');
require('dotenv').config();
const mongoose = require('mongoose');
const PORT = process.env.PORT || 3001;
const BASE_URL = process.env.REACT_APP_BASE_URL;
console.log(BASE_URL)
const itemRoutes = express.Router();
let Comment = require('./comment.model');
app.use(cors());
app.use(bodyParser.json());
mongoose.connect(BASE_URL, { useNewUrlParser: true })
const connection = mongoose.connection;
connection.once('open', function () {
console.log('Connection to MongoDB established succesfully!');
});
let collection = connection.collection("posts_with_tags_test");
collection.createIndex(
{
postContent: 'text',
title: 'text'
}
);
// Serve static assets
if (process.env.NODE_ENV === 'production') {
app.use(express.static('build'));
}
itemRoutes.route('/').get(async (req, res) => {
let collection = connection.collection("posts_with_tags_test");
let response = await collection.find({})
.toArray();
res.send(response);
});
itemRoutes.route('/search').post(async (req, res) => {
let result = await connection.collection("posts_with_tags_test").find({
$text: {
$search: req.body.searchString
}
}).toArray();
res.send(result);
});
itemRoutes.route("increment/:id"").post(async (req, res) => {
const { id } = req.params;
collection.updateOne({ _id: id }, { $inc: { views: 1 } });
return res.status(200).json({ msg: "OK" });
});
itemRoutes.route('/comments').get(async (req, res) => {
let collection = connection.collection("comments");
let response = await collection.find({})
.toArray();
res.send(response);
});
itemRoutes.route('/comments')
.post((req, res) => {
res.setHeader('Content-Type', 'application/json');
let comment = new Comment(req.body);
comment.save()
.then(comment => {
res.status(200).json({ comment })
})
.catch(err => {
res.status(400).send('failed')
})
});
app.use('/', itemRoutes);
app.use('/comments', itemRoutes);
app.use('/search', itemRoutes);
app.use('/increment', itemRoutes);
app.listen(PORT, function () {
console.log('Server is running on' + ' ' + PORT);
})

I think there are two problems in frontend and backend respectively.
Front-end
You should use post variable as a state variable so as to re-render then component when changes are made on post.
Back-end
There is no issue with increasing view in your code.
Here, you need to return success status.

The function incrementViews only increments views on the frontend and never sends any data to the API. One way you can make it work is as follows:
server.js
itemRoutes.route("/increment/:id").post(async (req, res) => {
const { id } = req.params;
collection.updateOne({ _id: id }, { $inc: { views: 1 } });
return res.status(200).json({ msg: "OK" });
});
React
const incrementViews = (id) => {
// Assuming your API server is running on port 5000.
fetch(`http://localhost:5000/increment/${id}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
})
.then((res) => res.json())
.then(console.log)
.catch(console.error);
};
Update
The reason you're getting 404 is a missing colon : in the route parameters.
// Notice the :id, colon is important.
itemRoutes.route("/increment/:id").post(async (req, res) => {
const { id } = req.params;
// ...
});
Here is a demo reproduced on Glitch. Removed database logic and just added a response messages.
I tested the demo using Postman and it works fine.
On a POST request to https://adaptive-sassy-legal.glitch.me/increment/123, should return a response as shown below.
{
msg: "itemsRoute increment.",
id: "123"
}
Update 2
Another thing which I forgot to mention in the previous update was to update the middleware.
// Use only `/increment` instead of `increment/:id`.
app.use("/increment", itemRoutes);
Here is an updated demo.

Related

how to split json into stages. Express API

So the problem takes place in architecture of my API, that I want to place into the array, called items[].
As we can see, here is a simple API wrote on express + mongodb.
But the question is how to place my data from post method into the global array called items[].
It should looks like:
So the code is:
const express = require('express')
const MongoClient = require('mongodb').MongoClient
const ObjectId = require('mongodb').ObjectId
const app = express();
let db;
const port = process.env.PORT || 3015
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.get('/', (req, res) => {
res.send('Hey')
})
app.get('/artists', (req, res) => {
let count = 10
let page = 0
req.query.count ? count = req.query.count : null
req.query.page ? (page = req.query.page * count, count = page + 5) : null;
db.collection('artists').find().toArray((err, docs) => {
if (err) {
console.log(err)
return res.sendStatus(500)
}
res.send(docs.slice(page, count))
})
}),
app.post('/artists', (req, res) => {
const artist = {
name: req.body.name,
photos: {
small: null,
large: null
},
status: null,
followed: false
}
db.collection('artists').insertOne(artist, (err, result) => {
if (err) {
console.log(err)
return res.sendStatus(500)
}
res.send(artist)
})
})
MongoClient.connect('***********', (err, client) => {
if (err) {
return console.log(err)
}
db = client.db('mongod');
app.listen(port, () => {
console.log(`API is listening on port ${port}...`)
})
})
You can do this easily with res.json() which sends the response as json data.
Change
res.send(docs.slice(page, count))
to
res.json({ items: docs.slice(page, count), status: "success" })

How come fetch only works here when I add an alert to the end of the line? Express + NodeJS + Fetch. What's a good fix here

I'm using NodeJS w/ Express to create a web app that records your audio using the VMSG library and posts the BLOB audio to my file system using HTTP Requests and multer. It also adds that instance of a recording into a MongoDB database.
I'm having an issue with the fetch command. It's not working unless I put an alert right after the fetch. The way I have it set up is that I have my main express app (index.js), and a router to the /recordingsDirectory (recordings.js) which is the endpoint for processing the posts. My main index HTML page uses Handlebars and uses a separate JS script (recorder.js) to 1) use the VMSG library and 2) fetch a POST to the /recordingsDirectory once someone submits the audio file w/ the name and the AudioBlob present. This is where I'm stuck. I can fetch in recorder.js with an alert line after the fetch, but I can't have the fetch on the end of the else if block by itself. I'd like to do it without this since the alert is ugly. A solution I've tried is that I tried to make the onsubmit function async and await fetch since I thought maybe it's waiting for a promise but that didn't work.
Here are the files. I commented CRITICAL and SUPER CRITICAL to the lines of code that you should check out and I think where the issues lie:
index.js
const express = require('express')
const handlebars = require('express-handlebars')
const path = require('path')
const XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest
const xhr = new XMLHttpRequest()
const db = require('./db')
const app = express()
const PORT = process.env.PORT || 8000
app.set('view engine', 'hbs')
app.engine('hbs', handlebars({
layoutsDir: path.join(__dirname, 'views', 'layouts'),
extname: 'hbs',
defaultLayout: 'index',
partialsDir: path.join(__dirname, 'views', 'partials'),
}))
app.use(express.json())
app.use(express.urlencoded({extended: false}))
app.use((err, req, res, next) => {
if (err instanceof SyntaxError && err.status === 400 && 'body' in err) {
return res.status(400).send({ status: 404, message: err.message })
}
next()
})
app.get('/', (req, res) => {
res.render('main', {
title: 'Main Page'
})
})
app.get('/recordings', (req, res) => {
var database = db.get().db('AudioJungle')
database.collection('recordings').find().sort({ "date": -1 }).toArray(function(err, docs) {
res.render('recordings', {
title: 'Recordings',
recordings: docs
})
})
})
// CRITICAL
app.use('/recordingsDirectory', require('./recordings/recordings'))
app.use(express.static('public'))
app.use('/scripts', express.static(path.join(__dirname, 'node_modules', 'vmsg')))
db.connect(function(err) {
if (err) {
console.log('Unable to connect to Mongo.')
process.exit(1)
} else {
app.listen(PORT, () => console.log(`Listening on Port: ${PORT}`))
}
})
process.on('SIGINT', function() {
db.close(function () {
console.log('Disconnected on app termination');
process.exit(0);
});
});
app.use((req, res, next) => {
res.status(404).send({
status: 404,
error: 'Not found'
})
})
recordings.js (Aka the /recordingsDirectory endpoint for a fetch POST)
const express = require('express')
const router = express.Router()
const multer = require('multer')
const fs = require('fs-extra')
const db = require('../db')
const { ObjectId } = require('bson')
const moment = require('moment')
const upload = multer({
storage: multer.diskStorage({
destination: (req, file, callback) => {
let path = './public/uploads'
fs.mkdirsSync(path)
callback(null, path)
},
filename: (req, file, callback) => {
createRecording(req).then((id) => {
var file_name = id + '.mp3'
callback(null, file_name)
})
}
})
})
var type = upload.single('audio-file')
// CRITICAL
router.post('/', type, (req, res) => {
console.log('made it')
res.status(200)
res.send('OK')
})
router.delete('/delete', (req, res) => {
deleteRecording(req.body._id).then((dbResponse) => {
if (dbResponse == null || dbResponse == undefined) {
res.status(400).json({ msg: 'ID already deleted' })
} else {
res.status(200)
}
})
})
router.get('/', (req, res) => {
var database = db.get().db('AudioJungle')
var recordings = database.collection('recordings')
recordings.findOne({"_id": ObjectId(req.query.id)}, function(err, result) {
if (err) throw err
if (result == null || result == undefined) {
return res.status(400).json({
status: 404,
error: 'Recording no longer in the database'
})
}
res.status(200)
res.json({
name: result.name,
date: result.date
})
})
})
async function createRecording(req) {
var database = db.get().db('AudioJungle')
var recordings = database.collection('recordings')
var audioObject = {
name: req.body.name,
date: moment().format('MMMM Do YYYY, h:mm:ss a')
}
var dbResponse = await recordings.insertOne(audioObject)
return dbResponse.insertedId
}
async function deleteRecording(id) {
var database = db.get().db('AudioJungle')
var recordings = database.collection('recordings')
var audioToDelete = {
_id: ObjectId(id)
}
var deleteResult = await recordings.deleteOne(audioToDelete)
return deleteResult
}
module.exports = router
And below is the Script the audio and name and tries to Fetch (where I need the alert for it to actually process into the /recordingsdirectory)
recorder.js
import { record } from "/scripts/vmsg.js";
let recordButton = document.getElementById("record");
var blobObj = null
recordButton.onclick = function() {
record({wasmURL: "/scripts/vmsg.wasm"}).then(blob => {
blobObj = blob
var tag = document.createElement("p")
tag.id="finishedRecording"
var text = document.createTextNode("Audio File Recorded")
tag.appendChild(text)
var element = document.getElementById("box")
element.appendChild(tag)
document.getElementById('box').appendChild(a)
})
}
let form = document.getElementById('mp3Form');
form.addEventListener("submit", submitAudio)
function submitAudio() {
var fileName = form.elements[0].value
if (fileName == "") {
alert('Please enter a name for your file')
} else if (blobObj != null) {
// CRITICAL
// SUPER CRITICAL WHERE FETCH DOESN'T WORK UNLESS I PUT AN ALERT AT THE END
const formData = new FormData()
formData.append('name', fileName)
formData.append('audio-file', blobObj)
const options = {
method: 'POST',
body: formData
}
fetch('/recordingsDirectory', options);
// If I comment an alert here, /recordingsDirectory will process the post since it console.logs 'made it'
} else {
alert('Record some Audio to upload')
}
}
Here's my file system.
Also, I'd like to mention that the fetch works properly on my Windows PC without having to add the alert, but it doesn't work without the alert on my macbook. If any one figures out a fix or an error in how I'm doing things to allow this please let me know. I've been stuck on this problem for a day now. Thanks a bunch!

Unable to query restful api to output json object in nodejs

I am building a restful API to fetch data from an external API (https://api.exchangeratesapi.io/latest). The fetch is successful. However, I want a situation where if you query like so "/api/rates?currency=EUR,GBP,USD", you will get
"rates": {
"EUR": 0.0377244605,
"GBP": 0.033795458,
"USD": 0.044824204
}
And if there is no query parameter, all the rates will be returned.
I tried using ternary operator to pull out the figures when a query parameter is present, but I get the error "TypeError: Cannot read property 'rates' of undefined"
Below is my app.js file
const express = require("express");
const request = require('request');
const apiCallFromRequest = require('./Requests')
const app = express();
const exchangeRouter = express.Router();
const port = process.env.PORT || 4001;
exchangeRouter.route(`/rates`)
.get((req, res) => {
const queryParameter = req.query;
// console.log(queryParameter.currency)
apiCallFromRequest.callApi(function(response) {
let cur = queryParameter.currency
res.json({
results: {
base: queryParameter.base ? queryParameter.base : response.base,
date: response.date,
rates: queryParameter.currency ? queryParameter.currency.response.rates : response.rates
}
})
res.end();
});
});
app.use('/api', exchangeRouter);
app.get('/', (req, res) => {
res.send('Welcome to my nodemon API')
});
app.listen(port, () => {
console.log(`Running on port ${port}`);
})
And my Request.js file
const request = require('request');
const URL = `https://api.exchangeratesapi.io/latest`;
const callExternalApiUsingRequest = (callback) => {
request(URL, { json: true }, (err, res, body) => {
if (err) {
return callback(err);
}
// console.log("body: ", body)
return callback({
base: body.base,
date: body.date,
rates: body.rates
});
});
}
module.exports.callApi = callExternalApiUsingRequest;
As I can see you're trying to filter the response. It would be better to inject your query params into your external api call.
You're expecting currency as a query parameter /api/rates?currency=EUR,GBP,USD on your api. There is no response under currency that's why queryParameter.currency.rates is undefined
According to https://exchangeratesapi.io/ you can use symbols to achieve the same result:
GET https://api.exchangeratesapi.io/latest?symbols=USD,GBP HTTP/1.1
you may use the same query params as the external api for yours and use a function to inject them into the external call
The solution. My app.js file should look like this:
const express = require("express");
const request = require('request');
const apiCallFromRequest = require('./Requests')
const app = express();
const exchangeRouter = express.Router();
const port = process.env.PORT || 4001;
exchangeRouter.route(`/rates`)
.get((req, res) => {
apiCallFromRequest.callApi(function(response) {
if (!base && !currency) {
return res.status(200).json({
results: {
"base": response.base,
"date": response.date,
"rates": response.rates
}
})
}
// If there is a query parameter but no value
if (!base) {
return res.status(400).json({ message: "Please specify your home currency" })
}
if (!currency) {
return res.status(400).json({ message: "Please specify exchange currency or currencies" })
}
// If there is query parameter and a value
const exchangeRates = currency.toUpperCase().split(',')
let currencyExchangeRates = {};
for (const exchangeRate of exchangeRates) {
//trim white space
const value = exchangeRate.trim();
currencyExchangeRates = { ...currencyExchangeRates, [value]: response.rates[value] }
}
console.log(currencyExchangeRates);
res.json({
results: {
"base": base,
"date": response.date,
"rates": currencyExchangeRates
}
})
res.end();
});
});
app.use('/api', exchangeRouter);
app.get('/', (req, res) => {
res.send('Welcome to my nodemon API')
});
app.listen(port, () => {
console.log(`Running on port ${port}`);
})

How to fetch API in React JS and save the data to MongoDB

I try to fetch an API and save the data to MongoDB, but i think i have some problem with my POST method..
I would like to store the data what i fetch from the API and then if i change data in the front i would like if it changed in the database
Here is my code snippet:
App.js file
class App extends React.Component {
state = {
name: "",
movie: [],
};
componentDidMount = () => {
this.getMovie();
this.displayMovie();
};
getMovie = () => {
axios.get("http://api.tvmaze.com/shows?page=1").then((response) => {
const data = response.data;
this.setState({
movie: data,
});
console.log("Data has been received");
console.log(this.state.movie);
});
};
displayMovie = () => {
axios({
url: "/",
method: "POST",
data: this.state.movie,
})
.then(() => {
console.log("Data has been sent to the server");
this.getMovie();
})
.catch((err) => {
console.log(err);
console.log("Internal server error");
});
};
render() {
return (
<div>
<form onSubmit={this.displayMovie}>
<button type="submit">Send</button>
<h2>Movie List</h2>
<div>
{this.state.movie.map((t) => {
return <p>{t.name}</p>;
})}
</div>
</form>
</div>
);
}
}
export default App;
server.js file
const express = require("express");
const mongoose = require("mongoose");
const bodyParser = require("body-parser");
const app = express();
const movieRouter = require("./routes/movie.routes");
const connect = () => {
return mongoose
.connect("mongodb://localhost:27017/movie", {
useNewUrlParser: true,
useUnifiedTopology: true,
})
.then(() => console.log("db connected"))
.catch((err) => console.log(err));
};
connect();
app.use(express.json());
app.use("/", movieRouter);
app.use(bodyParser.json({ limit: "50mb", extended: true }));
app.use(bodyParser.urlencoded({ limit: "50mb", extended: true }));
app.listen(5000, () => {
console.log("app is running on 5000");
});
model Schema
const mongoose = require("mongoose");
const movieSchema = new mongoose.Schema({
name: {
type: String,
},
});
const MovieSchema = mongoose.model("movieSchema", movieSchema);
module.exports = MovieSchema;
service js file
const MovieSchema = require("../models/movie.models");
const getMovie = async () => {
const movie = await MovieSchema.find();
return movie;
};
module.exports = {
getMovie,
};
controller file
const getMovie = async (req, res, next) => {
try {
const movie = await movieService.getMovie();
res.status(200).json({ result: movie });
} catch (err) {
next(err);
}
};
module.exports = {
getMovie,
};
router file
const express = require("express");
const router = express.Router();
const movieController = require("../controllers/movie.controllers");
router.get("/", movieController.getMovie);
module.exports = router;
What am i doing wrong ? I have POST http://localhost:3000/ 404 (Not Found) error message.
Thank you for help!
First of all your POST route handler is missing (ref: Andrew Nolan).
Secondly your react code App.js componentDidMount method is calling getMovie and straight after calling displayMovie. It has to wait till getMovie method returns the results. So i placed promises to solve it.
class App extends React.Component {
state = {
name: "",
movie: [],
};
async componentDidMount() {
try {
await this.getMovie();
this.displayMovie();
} catch (e) {
console.log(e);
}
};
getMovie = async () => {
return new Promise((resolve, reject) => {
axios.get("http://api.tvmaze.com/shows?page=1").then(({ data }) => {
this.setState({
movie: data,
});
resolve();
}).catch(e => reject(e));
});
};
displayMovie = async () => {
axios({
url: "/",
method: "POST",
data: this.state.movie,
})
.then(() => this.fetchMovie())
.catch((err) => console.log(err));
};
render() {
return (
<div>
<form onSubmit={this.displayMovie}>
<button type="submit">Send</button>
<h2>Movie List</h2>
<div>
{this.state.movie.map((t) => {
return <p>{t.name}</p>;
})}
</div>
</form>
</div>
);
}
}
export default App;
You don't need to send status code 200. json method will stringify the json object and attach 200 status code as default.
res.status(200).json({ result: movie }); // too long
res.json({ result: movie }); // better practice
res.json({ movie }); // even better
When you are creating mongoose schema, you don't need to explicitly declare type of the data in object literals unless you want to add other configurations.
const movieSchema = new mongoose.Schema({
name: {
type: String,
},
});
const movieSchema = new mongoose.Schema({
name: String // better
});
In your code, you have only specified a GET route for /. You also need to specify a POST route too.
const express = require("express");
const router = express.Router();
const movieController = require("../controllers/movie.controllers");
router.get("/", movieController.getMovie);
router.post("/", movieController.saveMovie);
module.exports = router;
I put in movieController.saveMovie as a placeholder for whatever gets invoked at this route. That part is up to you. I didn't see a method for it in your code snippets to save it.
You are also missing post controller
just like get, add post controller in controller file too.
const postMovie = async (req, res, next) => {
const {name} = req.body;
try {
const newMovie = new Movie({
name,
});
const movie = await newMovie.save();
res.status(200).json({ result: movie });
} catch (err) {
next(err);
}
};
module.exports = {
postMovie,
};

API get call returns status 400

I'm trying to learn how to use the MERN stack. I've been following a YouTube tutorial (https://www.youtube.com/watch?v=WT67-OETeGU). Currently, I've defined and created my server.js file as follows:
const express = require("express");
const app = express();
const bodyParser = require("body-parser");
const cors = require("cors");
const mongoose = require("mongoose");
const todoRoutes = express.Router();
const PORT = 4000;
let Todo = require("./todo.model.js");
app.use(cors());
app.use(bodyParser.json());
mongoose.connect("mongodb://127.0.0.1:27017/todos", {
useUnifiedTopology: true,
useNewUrlParser: true
});
const connection = mongoose.connection;
todoRoutes.route("/").get(function(req, res) {
Todo.find(function(err, todos) {
if (err) {
console.log("error getting data");
} else {
res.json(todos);
}
});
});
todoRoutes.route("/:id").get(function(req, res) {
let id = req.params.id;
Todo.findById(id, function(err, todo) {
if (err) {
console.log("error finding todo object with id: " + id);
} else {
res.json(todo);
}
});
});
todoRoutes.route("/add").post(function(req, res) {
let todo = new Todo(req.body);
todo
.save()
.then(todo => {
res.status(200).json({ todo: "Todo added successfully" });
})
.catch(err => {
res.status(400).send("failed to save new todo");
});
});
todoRoutes.route("/update/:id").post(function(req, res) {
let id = req.params.id;
Todo.findById(id, function(err, todo) {
if (!todo) {
res.status(400).send("cant update id: " + id);
} else {
todo.todo_description = req.body.todo_description;
todo.todo_responsible = req.body.todo_responsible;
todo.todo_priority = req.body.todo_priority;
todo.todo_completed = req.body.todo_completed;
todo
.save()
.then(todo => {
res.json("Todo updated");
})
.catch(err => res.status(400).send("update not possible"));
}
});
});
app.use("./todos", todoRoutes);
I am trying to test my API out, so I installed Postman and set it up. I'm trying to make a GET request to: http://localhost:4000/todos. However, Postman only returns a 404, saying "Could not get any response".
I've run the commands mongod and mongo on my terminal to get Mongo running.
Any suggestions on where I've gone wrong?
It seems you are setting your endpoints as relative paths. Can you remove the dot proceeding "/todos"? in app.use("./todos", todoRoutes);
`

Categories