I was wondering if there's a way to insert documents into a MongoDB collection directly from a React component.
My current personal project (for training purpose) is a little chat web application. So for example, when my user wants to post a new message in a room, the input component should look something like this:
var NewMessageInput = React.createClass({
postMessage: function(value) {
db.collection.insert({
content: value
});
},
render: function() {
return (
<div>
<input onSubmit={() => this.postMessage(value)}>Type your message here</input>
</div>
);
}
});
I know how to get an app running with Express, React, and MongoDB but I'm only able to insert document with the mongo shell and querying them at the route loading. Plus, I would like to use only react-router for my routes and that's why I have this question.
I'm guessing you'll need the database on the server, so you may need an API to post the data since the database itself isn't on the client.
I use Superagent for sending the data and Mongoose to create a schema and mongo database.
messageModel.js
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
// create a schema
const messageSchema = new Schema({
// You may need to add other fields like owner
addedOn: String,
message: String,
});
const Message = mongoose.model('Message', messageSchema);
module.exports = Message;
server.js
import Message from './models/messageModel';
mongoose.connect('mongodb://user:pass#localhost:port/database');
app.post('/api/messages', (req, res) => {
const doc = new Message({ message: req.body.message })
doc.save();
});
client.js
import React, { Component } from 'react';
import request from 'superagent';
class componentName extends Component {
constructor(props) {
super(props);
this.state = {
message: '',
};
this.handleMessageInput = this.handleMessageInput.bind(this);
}
handleMessageInput(e) {
this.setState({ message: e.target.value });
}
handleSubmitMessage() {
console.log('starting to submit profile');
if (this.state.isFormFilledProfile) {
console.log('Profile Form appears filled');
const data = {
message: this.state.message,
};
request
.post('/api/messages')
.send(data)
.set('Accept', 'application/json')
.end((err, res) => {
if (err || !res.ok) {
console.log('Oh no! err');
} else {
console.log('Success');
}
});
}
}
render() {
return (
<div>
<div>
<form onSubmit={this.handleSubmitMessage}>
<input
onChange={this.handleMessageInput}
value={this.state.message}
/>
<button type='Submit' value='Submit'>Submit</button>
</form>
</div>
</div>
);
}
}
export default componentName;
You may need to also go through the react forms guide here.
All the best!!!
Ok so because React is a front end framework you will not have access to things on your backend such as methods like db.collection.insert(). You will in tern has a separation of front end code and back end code. They will talk to each other through HTTP requests (GET, POST, PUT, DELETE).
Side note just to clarify. You will still need express for routing as well as react-router. They do different types of "routing". Express will handle routing of mainly your API, all the data calls that your front end will make to it. React-router handles page changes on your front end, right within the bowser. This keeps your page from reloading each time the user moves around.
So lets get into a little code.
On your back end you will need an express endpoint for your app to talk to. In this example I will show use with the package mongoose as it is a tool that is commonly used with a node.js backend.
https://www.npmjs.com/package/mongoose
var mongoose = require('mongoose');
var Message = require('../models/message');
app.post('/message', (req, res) => {
var newMessage = new Message(req.body);
newMessage.save((err, doc) => {
if (err) {
res.send(err);
} else {
res.send(doc);
}
});
});
This code will handle a post request at the url "/message". It will proceed to create a new message from the data in the request (req) body. Then save that to the database. After the save is successful it will send back the document that was just saved.
In mongoose we create a schema for each of our data models like such:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var MessageSchema = new Schema({
content: {
type: String,
},
createdAt: {
type: Date,
default: Date.now
}
});
var Message = mongoose.model('Message', MessageSchema);
module.exports = Message;
Now on the front end / React side of things we need to send our message data to the backend to save. We can use a package like axios to make clean promise based HTTP requests from the browser.
https://www.npmjs.com/package/axios
var NewMessageInput = React.createClass({
postMessage: function(value) {
axios.post('/message', {
content: value
})
.then(function (message) {
console.log(message);
})
},
render: function() {
return (
<div>
<input onSubmit={() => this.postMessage(value)}>Type your message here</input>
</div>
);
}
});
And that should do it!
For a backend example check out this repo I have setup. It's very basic but will be a good example of a backend with mongoose for reference.
https://github.com/Alexg2195/simple-dev-example
Also here is a bit of some side content I created for teaching React that may be a good reference.
https://github.com/Alexg2195/UT-ReactJS
Related
I know there are a lot of other "works in postman and not in browser" posts, but I've read through them and cannot find anything to give direction on what I'm not catching here. Most of those had to do with proxy issues, but I dont have any proxy's set up.
I recently changed from using a pymongo backend to mongoose/express. My find() works for the get all clients just fine on the browser side, but the findOne() get comes back undefined (I was getting an unexpected token JSON error but that is resolved although I dont know what actually fixed it), yet in Postman it brings exactly what I'm looking for. I'm assuming its something simple, but I can't seem to spot it.
Backend-
index.js
const express = require("express")
const mongoose = require("mongoose")
const cors = require('cors')
const clientRoutes = require("./routes/clientRoutes")
const contractRoutes = require("./routes/contractRoutes")
const bodyParser = require('body-parser');
mongoose
.connect("MONGODB URL", { useNewUrlParser: true })
.then(() => {
const app = express()
app.use(express.json())
app.use(cors())
app.use(bodyParser.json());
app.use("/api", clientRoutes)
app.use("/api", contractRoutes)
app.listen(5000, () => {
console.log("Server has started")
})
})
Schema
const mongoose = require("mongoose")
const schema = mongoose.Schema({
clientId: Number,
firstName: String,
lastName: String,
phone: String,
contracts: [{
contractId: Number,
authNumber: String,
contType: String,
contHours: Number,
contStartDate: Date,
contEndDate: Date
}],
})
module.exports = mongoose.model("Client", schema)
routes-
const express = require("express")
const Client = require("../models/Client.js")
const router = express.Router()
//Client routes
router.get("/clients", async (req, res) => {
const clients = await Client.find()
res.send(clients)
})
router.get("/clients/:clientId", async (req, res) => {
try {
const client = await Client.findOne({ clientId: req.params.clientId })
res.send(client)
} catch {
res.status(404)
res.send({ error: "Client not found"})
}
})
React frontend component making the request-
import React from 'react';
import PropTypes from 'prop-types';
import { withRouter } from 'react-router-dom';
import ChartNav from './ChartNav';
import ClientContext from './ClientContext';
class ClientChart extends React.Component {
static get propTypes() {
return {
match: PropTypes.any,
clientId: PropTypes.any
}
}
constructor (props){
super(props);
this.state = {
clientId: this.props.match.params.clientId,
client: {},
isLoading: true,
errors: null
};
console.log(this.state.clientId)
}
componentDidMount(){
fetch(`http://localhost:5000/api/clients/${this.state.clientId}`)
.then(res => res.json())
.then(
result => {
let client = JSON.parse(result.data);
this.setState({
isLoading: false,
client: client,
});
}, [])
.catch(error => this.setState({
error: error.message,
isLoading: false,
}));
}
console and response
404
XHR GET http://localhost:5000/api/clients/undefined
error "Client not found"
So in trying to track it down, I switched clientId back to id (which I had been using previously, and changed the prop in the DB for 1 client back to id to test), and calling console.log after the initial response from the fetch showed the data coming through. When I setState from that initial response, all props populated where they should. In reverting the id back to clientId and changing the routes, and using a client with the clientId field, etc., nothing works again. So if anyone knows why React is happy with id but not clientId as an identifier, please let me know. Even weirder is that its able to call all the other clients who I still have listed with clientId, and the routes are calling by clientId, not id... so Im at a total loss as to whats happening under the hood.
Below is the working get call (I also threw in axios at one point in trying to track it down and left it there, but initially it did not make any difference).
axios.get(`http://localhost:5000/api/clients/${this.state.id}`)
.then((response) => {
const data = response.data;
console.log(response.data);
this.setState({
client: data,
isLoading: false,
});
}, [])
I am in a programming bootcamp and we are currently learning about fullstack JavaScript. We are using the Express framework and I have opted to use a SQLite3 database. This is a basic app for maintaining books at a local community center. I am required to write up to 3 unit tests and the one that I am having difficulty with is the GET / route. This route is intended to pull data from the database and render them to create a list of books currently in the database when you visit the root route.
I want to test whether the bookList variable containing the awaited Book.findAll() Sequelize function is actually sending an array of objects containing row data to the Pug template engine to render the list of books.
When I host locally, this is working as intended. I am just puzzled on how to have that reflected in my unit tests. I've attached the routes/index.js, views/index.pug & the test/readOp_test.js containing all of my tests including the one for the GET / route. Please let me know if I need to share more of the code and will reply with what is needed for anyone willing to help me.
readOp_test.js
let chai = require('chai');
let chaiHTTP = require('chai-http');
let chaiPromised = require('chai-as-promised');
let application = require('../app');
let expect = require('chai').expect;
chai.use(chaiHTTP);
chai.use(chaiPromised);
chai.should();
// Unit Test Suite
describe('Unit Tests for Endpoints to meet Code Louisville Project Requirements', () => {
//Test Spec for READ ALL operation
/* it('should pull data from all rows in the database', (done) => {
chai.request(application)
.get('/')
.end((err, res) => {
res.body.should.be.a('object');
done();
})
}); */
//Test Spec for READ-by-id operation
it('should pull data from the below id', (done) => {
const id = 2;
chai.request(application)
.get(`/${id}`)
.end((err, res) => {
res.body.should.be.a('object');
done();
})
});
it('should return status 404 when READING the below id which is not in the database', (done) => {
const id = 12;
chai.request(application)
.get(`/${id}`)
.end((err, res) => {
res.should.have.status(404);
done();
})
});
//Test Spec for CREATE operation
it('shouldnt POST the below book entry since the title value is an empty string.', async() => {
let res = await chai
.request(application)
.post('/')
.type('form')
.send({title: '', author: "al-Gibber", genre: "Math", language: "Arabic"})
expect(res.status).to.equal(500)
});
it('should POST the below book entry since all properties contain actual string values.', async() => {
let res = await chai
.request(application)
.post('/')
.type('form')
.send({title: 'Gibberish', author: "al-Gibber", genre: "Math", language: "Arabic"})
expect(res.status).to.equal(200)
});
});
routes/index.js
// Requiring Express to enable access to the framework's methods, properties, & other tools.
const express = require('express');
// Router handles HTTP requests.
const router = express.Router();
// These two variables are ensuring `routes/index.js` has access to the database & its models.
const db = require('../db');
const { Book } = db.models;
//This is a convenient function that handles async/await.
function asyncHandler(cb){
return async(req, res, next) => {
try {
await cb(req, res, next)
} catch(error){
// Forward error to the global error handler
next(error);
}
}
}
//This route handles the initial load of the app, the root route, & will draw data from each row in the database & display it on the homepage.
//This route is a READ operation distinctly a READ ALL operation.
router.get('/', asyncHandler(async (req, res) => {
const bookList = await Book.findAll();
//The use of .render() method ensures that the `index.pug` template is rendered when user visits the root directory.
//{ bookList } is an object containing data from each row in the database to be used in the Pug template.
res.render('index', {bookList});
}));
//This route handles adding a book to the app's database by rendering the newBook.pug template which contains a form to collect the column data for the database.
//This route is the beginning of the CREATE operation for this app.
router.get('/new', (req, res) => {
res.render('newBook', { book: {}});
});
//This route handles the data collected by newBook.pug's form. It creates the database entry and posts the data.
//This route is the closure for the CREATE operation for this app.
router.post('/', asyncHandler(async (req, res) => {
console.log(req.body);
let book;
try {
book = await Book.create(req.body);
console.log(book);
res.redirect('/');
} catch (error) {
throw error;
}
}));
//This route handles rendering found data for each book that would be clicked on in the index.pug template.
//This route is another distinct READ operation that is reading an entry's data based on the primary key ID selected on index.pug.
router.get("/:id", asyncHandler(async (req, res) => {
const book = await Book.findByPk(req.params.id);
if(book) {
res.render("bookViewing", { book });
} else {
res.sendStatus(404);
}
}));
//Ensures that all routes written in this folder can be used in the root's `app.js` file.
module.exports = router;
index.pug
doctype html
html(lang="en")
head
title Library
link(rel='stylesheet', href='/static/stylesheets/style.css')
body
div#app
div#container
h1 Library
h2 Our Book Selection
each book in bookList
book
p
span= book.title
|
a(href=`/${book.id}`) Learn More
p This library is open to all in our local community!
a(href="/new") Add New Book
Screenshot of the correctly rendered index.pug template
Screenshot of rendered PUG template
I have a single webpage that initially has two form inputs, one for a list of names and another for the title of a game. I've written some javascript/jquery that takes the X names and creates X more form inputs meant for each person's specific score. The javascript then creates the following variables upon the clicking of the names/scores form's submit button:
gameTitle = Monopoly
users = [Bob, Bill, Jim, Janet]
scores = [100, 110, 90, 80]
positions = [2, 1, 3, 4]
I then have a MongoDB schema set up as such:
const SessionSchema = new mongoose.Schema({
gameTitle: String,
users: [],
scores: [],
positions: []
});
And a Node.js handler as such:
const express = require('express');
const router = express.Router();
const bodyParser = require('body-parser');
const timestamps = require('mongoose-timestamp');
router.use(bodyParser.urlencoded({ extended: true }));
router.use(bodyParser.json());
const Session = require('./Session');
//Post a session to the database
router.post('/', function(req, res) {
Session.create({
gameTitle : req.body.gameTitle,
users : req.body.user,
scores : req.body.score,
positions : req.body.position
},
function (err, session) {
if (err) return res.status(500).send("There was a problem adding the information to the database");
res.status(200).send(session);
});
});
Using Postman I can see that posting works when I use this format:
Postman POST
Postman GET
How do I take the created javascript variables and, also upon the clicking of the names/scores form's submit button, POST them through the API and into the MongoDB database?
Apologies if I have missed any important information/code - I haven't fully wrapped my head around how the backend stuff works.
You need to register your Schema:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const SessionSchema = new mongoose.Schema({
gameTitle: String,
users: [],
scores: [],
positions: []
});
module.exports = mongoose.model('session', SessionSchema);
And here you need to use the mongo schema model, like this:
const express = require('express');
const router = express.Router();
const bodyParser = require('body-parser');
const timestamps = require('mongoose-timestamp');
router.use(bodyParser.urlencoded({ extended: true }));
router.use(bodyParser.json());
const SessionSchema = require('./Session'); // register the mongo model
const mongoose = require('mongoose');
const Session = mongoose.model('session');
//Post a session to the database
router.post('/', function(req, res) {
const new_session = {
gameTitle : req.body.gameTitle,
users : req.body.user,
scores : req.body.score,
positions : req.body.position
};
new_session.save((err, saved_session) => {
if(err) {
res.json(err);
} else {
res.json(saved_session);
}
});
});
Sounds like you have the backend working. What you're missing is the API request. Since your website is not under the same host:port than your API server, when doing it from the browser you'll face CORS issues. Let's get to that later:
First, you'll be making an API call. You can use axios or fetch. Let's go with fetch here:
fetch(url, {
body: JSON.stringify(yourJavascriptVariablesAsAnObject),
headers: {
'Content-Type': 'application/json'
},
method: 'POST',
})
.then(response => {
// here you can check for status if you want.
// ...
return response.json(); // assuming server returns JSON.
})
.then(responseBody => {
// Do something with the server's response body
});
Now for the CORS problem, if your client app is from create-react-app or at least you're using webpack-dev-server, you can proxy request really easy.
If you're not, then you need to allow CORS on your nodeJS server. The simplest way is to use a library.
PS: CORS basically means you can't do requests from a browser to a service living in a different `url:port:, unless that service explicitly says it's ok.
A third option would be putting both UI and server project behind a Web server like Nginx and proxy the requests, but that sounds too complex for what you need.
I am not getting the user input value at the server side to submit in the database using React.js and Node.js. I am providing my code below.
Additem.js:
import React, { Component } from 'react';
import ItemService from './ItemService';
class AddItem extends Component {
constructor(props){
super(props);
this.state = {value: ''};
this.addItemService = new ItemService();
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event){
this.setState({value: event.target.value});
}
handleSubmit(event){
event.preventDefault();
this.addItemService.sendData(this.state.value);
this.props.history.push('/');
}
render() {
return (
<div className="container">
<form onSubmit={this.handleSubmit}>
<label>
Add Item:
<input type="text" value={this.state.value} onChange={this.handleChange} className="form-control"/>
</label><br/>
<input type="submit" value="Submit" className="btn btn-primary"/>
</form>
</div>
);
}
}
export default AddItem;
itemService.js:
import axios from 'axios';
class ItemService {
sendData(data) {
axios.post('http://localhost:8888/add/post', {
item: data
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
}
}
export default ItemService;
Here from react front end I am sending the form data to the server which is given below.
route/route.js:
var task=require('../controller/controller.js');
module.exports = function(app) {
app.get('/item',task.getUserData);
app.post('/add/post',task.userDataSubmit);
app.post('/edit/:id',task.userDataEdit);
app.post('/update/:id',task.userDataUpdate);
app.post('/delete/:id',task.userDataDelete);
}
controller/controller.js
var mongoJs=require('mongojs');
var dateTime = require('node-datetime');
var crypto = require("crypto");
var config=require('../config/config.js');
var database='mern-crud';
var collections=['books','items'];
var db=mongoJs(config.url+database, collections);
db.on('connect', function () {
console.log('database connected')
});
exports.getUserData=function(req,res){
db.items.find({},function(err,docs){
if (!err) {
if (docs) {
res.json(docs);
}
}
})
}
exports.userDataSubmit=function(req,res){
console.log('req',req.body);
var data={
name:req.body.item
}
db.items.insert(data,function(err,docs){
if (!err) {
if (docs) {
res.json('Item added successfully');
}
}else{
res.json('unable to save to database');
}
})
}
exports.userDataEdit=function(req,res){
}
exports.userDataUpdate=function(req,res){
}
exports.userDataDelete=function(req,res){
}
My problem is after submitting the form data these are not uploaded into the database even console.log('req',req.body); is not executing at all. Here I need the form data should be stored into MongoDB.
define the initial state of value not in the constructor, but in componentDidMount. Then use componentWillRecieveProps and componentShouldUpdate to handle your local stat updates.
It might be the issue of CORS
The easiest way is to install node package
https://www.npmjs.com/package/cors
var cors = require('cors')
var app = express()
app.use(cors())
Try using
debugger;
remember to keep you console open when u run the application using
debugger keyword, otherwise it won't trigger
in your application to add a breakpoint and control the execution step by step
and see whether you get the input data to the point where the req goes to the server with neede input data. If yes then it might be cors policy
use this npm package
https://www.npmjs.com/package/cors
var express = require('express')
var cors = require('cors')
var app = express()
app.use(cors())
app.get('/products/:id', function (req, res, next) {
res.json({msg: 'This is CORS-enabled for all origins!'})
})
app.listen(80, function () {
console.log('CORS-enabled web server listening on port 80')
})
remember to add this in the topmost lines like shown above. that's why I have put the entire code just so that you can understand why we do it that way.
This is a middle-ware that gets added to your HTTP pipeline so u have to be well aware how to order them and what not
I am a beginner in VueJs and Expressjs. I am trying to make frontend side by Vuejs and backend by ExpressJs. I send a post request to the backend (expressJs) and :
1- Response is undefined
2- At the same time I can see 2 requests in chrome development tools. One is Option and another one is Post.
3- With postman there is no problem at all.
Here is the code of app.js in express
console.log('Server is running')
const express = require('express'),
bodyParser = require('body-parser'),
cors = require('cors'),
morgan = require('morgan');
app = new express();
//Setup middleware
app.use(cors());
app.use(morgan('combined'))
app.use(bodyParser.json())
app.post('/register', (req, res, next) => {
res.send({
message: `Hello ${req.body.email}! your user was registered!`
})
});
app.listen(8081);
And here is the code in VueJs :
// Api Setting
import axios from 'axios'
export const HTTP = axios.create({
baseURL: `http://localhost:8081`
});
// AuthenticationService
import { HTTP } from '../services/Api'
export default {
register(credentials) {
HTTP.post('register', credentials);
}
}
// Register Component
export default {
data() {
return {
email: '',
password: ''
};
},
methods: {
async register() {
const response = await AuthenticationService.register({
email: this.email,
password: this.password
});
console.log(response); // the value is undefined
}
}
};
I really don't know what I missed here that I get an undefined response and 2 requests at the same time. I appreciate any hint.
Whole code on github repo : here
Maybe. Authentication.register is not returning anything or more specifically a Promise which should be used to populate const response in the await call.
Try returning something like so: return HTTP.post('register', credentials); inside register.
For this to work though, HTTP.post('register', credentials) should also return something.
I use JSON.stringify to send the data, you are sending the objects directly, so
register(credentials) {
HTTP.post('register', credentials);
}
becomes
register(credentials) {
HTTP.post('register', JSON.stringify(credentials));
}