Preamble: I'm new to web dev so maybe this might be a very basic question for you vets.
I'm using MVC architecture pattern for this basic app. I've models (MongoDB), views (Express Handlebars), and controllers (functions that take in req, res, next and returns promises (.then > JSON is returned, .catch > error is returned). I'll be routing the paths reqs to their corresponding api endpoints in the controllers.
This makes sense (right?) when I'm purely working on API calls where JSON is the res. However, I also want to call these api endpoints > get their res.json > and use that to render my HTML using Handlebars. What is the best way to accomplish this? I can create same controllers and instead of resp being JSON, I can do render ("html view", res.json). But that seems like I'm repeating same code again just to change what to do with the response (return JSON or Render the JSON).
Hope I'm making sense, if not, do let me know. Please advise.
p.s. try to ELI5 things for me. (:
Edit:
//Model Example
const Schema = require('mongoose').Schema;
const testSchema = new Schema({
testText: { type: String, required: true },
});
const Test = mongoose.model('Test', testSchema);
module.exports = Test;
//Controller Example
const model = require('../models');
module.exports = {
getAll: function(req, res, next) {
model.Test.find(req.query)
.then((testItems) => {
!testItems.length
? res.status(404).json({ message: 'No Test Item Found' })
: res.status(200).json(testItems);
})
.catch((err) => next(err));
},
};
//Route Example
const router = require('express').Router(),
controller = require('../controllers');
router.get('/', controller.getAll);
module.exports = router;
I want the endpoints to return JSON and somehow manage whether to render (if the req comes from a browser) or stay with JSON (if called from Postman or an API web URL for example) without repeating the code. I'm trying to not create two endpoitns with 99% of the code being the same, the only difference being .then > res.status(200).json(testItems); vs .then > res.status(200).render('testPage', { testItems}).
For postman you could check the existence of postman-token in req.headers, then you could render accordingly, something like this:
req.headers['postman-token'] ? res.json({ /* json */ }) : render('view', {/ * json */});
If you want to go with checking postman token then you can use similar to method1.
if you want to check with query params in this case you can get json response or html even from browser for future use also and is not dependent on postman then use similar to method2 of the following example.
const express = require('express')
const bodyParser = require('body-parser')
const app = express()
app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.json())
const port = 5000
app.get('/method1', (req, res) => {
const isJSONResp = req.headers['postman-token']
const resp = { status: "hello" }
if (isJSONResp) {
res.json(resp)
} else {
res.render('some.html', resp)
}
})
app.get('/method2', (req, res) => {
const params = req.params
const resp = { status: "hello" }
if (params.resp === 'json') {
res.json(resp)
} else {
res.render('some.html', resp)
}
})
app.listen(port, () => console.log(`Example app listening on port ${port}!`))
Related
This question already has answers here:
Access current req object everywhere in Node.js Express
(2 answers)
Closed 8 months ago.
This post was edited and submitted for review 8 months ago and failed to reopen the post:
Original close reason(s) were not resolved
I have the following lines of code:
const express = require('express');
const app = express()
// ... defining the routes, app.get('/api/users', (req, res, next)=>{ }) ...etc
app.listen(3000, ()=> console.log('Listening on port 3000...'))
module.exports = app
I want to be able to read the request object outside an express middleware.
I have another file called mongoose_models.js, inside that file, I don't have the access to the express middleware arguments (req, res, next).
And the only option I have for reading the request body from that file is to import the app and somehow read the request Object.
NodeJs is event-driven, so there must be a way somehow to do so, for instance, inside the file mongoose_models.js I would have maybe something like this code:
// mongoose_models.js
// ... some code
const app = require('../app.js')
app.on('request', (req)=>{
// here I have the request
})
or maybe if express supports:
// mongoose_models.js
// ... some code
const { req } = require('express')
console.log(req.body) // ? maybe something like that ?
or maybe if express supports too:
// mongoose_models.js
// ... some code
const app = require('../app.js')
app.onRequest((req, res) => {
// here I have the access to the request object
})
Is there a way to reach the request object without having to be inside an express middleware in NodeJS?
edit:
Some of you asked me to provide the source code, unfortunately, I wanted to provide a stackblitz or code sandbox instance, but I didn't know how to set up the connections to the database.
Anyway, the following is the file structure of the sample project:
app.js file (full code):
const express = require('express')
const app = express()
const mongoose = require('mongoose')
const RoomModel = require('./mongoose_models')
app.use((req, res, next) => {
// this middleware is the "protect" middleware, it validates a JWT (JSON web token), decodes it, and then stores the user it finds to the req object:
// .... etc some code
// decode the JWT .. some code
// find the user in the DB const userDoc = await UserModel.findOne({ _id: decodedJWT.id )})
const userDoc = {
id: 'abc-123-edf-cds-123-321-qu5-eu4-dc9-182',
name: 'Murat',
// and some other fields ... etc
}
req.$loggedInUser = userDoc
})
app.get('/rooms', async(req, res, next) => {
const docs = await RoomModel.find({})
res.status(200).json({
message: 'here are all the rooms',
results: docs.length,
data: docs,
})
})
app.post('/rooms', async(req, res, next) => {
const doc = await RoomModel.create(req.body)
res.status(201).json({
message: 'the new room which got created:',
data: doc,
})
})
// connecting to the database:
mongoose.connect(
'mongodb+srv://USERNAME:PASSWORD#YOUR_CLUSTER.mongodb.net/?retryWrites=true&w=majority'
)
// starting the HTTP service:
app.listen(3000, () => console.log('app listening on port 3000...'))
mongoose_models.js file (full code):
const mongoose = require('mongoose')
const roomSchema = new mongoose.Schema({
name: String,
by: mongoose.Schema.ObjectId,
})
roomSchema.pre('save', function(next) {
// Here I want to make the by field be the req.$loggedInUser.id but I can't because I have no way to read the request object
const doc = this
// doc.by = req.$loggedInUser.id // < ----- 👈👈👈 HERE, I can't reach the req object
next()
})
const RoomModel = mongoose.model('Room', roomSchema, 'rooms')
module.exports = RoomModel
NodeJS is event driven, so there must be a way somehow to do so, for
instance, inside the file mongoose_models.js I would have maybe
something like this code:
// mongoose_models.js
// ... some code
const app = require('../app.js')
app.on('request', (req)=>{
// here I have the request
})
This approach is, essentially, middleware. So write is as middleware.
const myMiddleware = (req, res, next) => {
// here you have the request
next(); // go to next middleware
}
module.exports = myMiddleware
Attaching something to listen for requests is done with use (for non-method specific functions) and post, get, etc. There is no on method or onRequest method.
// mongoose_models.js
// ... some code
const { req } = require('express')
console.log(req.body) // ? maybe something like that ?
The request object doesn't exist until the client makes a request to the server.
You get a new request object each time a request is made.
The server might be handling multiple requests at the same time.
So no, you can't do anything like that.
Is there a way to reach the request object without having to be inside an express middleware in NodeJS?
No.
I am building an api with express and mongoose and im usingdb.collection(collection).find({}) expecting that i get all of my docs back (3 test docs in total) as seen from tutorials. It doesnt give me any errors when im executing a req from postman and that confuses me as im geting nothing, not even an empty object or array as a response. I have tried different examples, from different tutorials but nothing works. Is the syntax wrong?
import express from 'express'
import './connection.js'
const router = express.Router
const db = mongoose.connection;
const dtb = db.useDb('main').collection('products')
router.get('/true', async(req, res) => {
try {
const products = await dtb.find({})
res.send(products).status(200)
} catch (err) {
console.log(err)
res.sendStatus(500)
}
})
So, after lookig again and again at my code I realized that the route /true was taken as a param by express as i have another route for app.get('/:_id', getItem).
After changing the route to /all/true parsing the docs into an array and after some debugging, my getAll function looks like this and finally works :
export const getAll = app.get('/all/true', async(req, res) => {
const products = await dtb.find({}).toArray().catch(console.error())
res.send(products)
})
In my express router I check if the data inserted on a form are valid then if they are I render another page passing form data. I would like to access the data I pass client-side. On the chat.ejs view I have a chatroom.js client file, I want to access the data there without having to access them in a script tag.
I thought about using Ajax but the only answer I found here on StackOverflow was marked as wrong, so how do I go about that?
router.js
module.exports=function(app) {
const express = require('express');
const router = express.Router();
const {check, validationResult} = require('express-validator');
const {matchedData} = require('express-validator/filter');
router.get('/', (req, res) => {
res.render('index', {
data: {},
errors: {}
})
});
router.post('/enter', [
check('username')
.isLength({min: 1})
.withMessage('Username is required').trim(),
check('room')//implement personalized check
], (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.render('index', {
data: req.body,
errors: errors.mapped()
})
}
else {
const data = matchedData(req);
return res.render('chat',{
user: data.username,
room:data.room
})
}
});
return router;
//MOVE TO SUPPORT
function find(name) {
return 1;
}
}
there is really nothing client-side so far so It seems useless just posting my views. Alternatively, I could use Ajax on client.ejs to handle the form submission but I would like to keep this clean and handle the routing with the router file.
I ended up creating two global variables in a script tag for my index.ejs page like this
<script>
var user = <%- JSON.stringify( user ) %>
var room = <%- JSON.stringify(room)%>;
</script>
and then I could access them in the chatroom.js file linked below
I'm created a chat-bot using 'botact' library, but
when I'm trying to verificate my bot on vk-community API working page I receive an error in 'Windows PowerShell' (Here I started the server for bot):
TypeError: Cannot read property 'fwd_messages' of undefined
at Botact.getLastMessage (C:\Users\whoami\Desktop\Bot-test\node_modules\botact\lib\utils\getLastMessage.js:2:11)
at Botact.module.exports (C:\Users\whoami\Desktop\Bot-test\node_modules\botact\lib\methods\listen.js:29:28).
The file 'getLastMessage.js' contains this code:
const getLastMessage = (msg) => {
if (msg.fwd_messages && msg.fwd_messages.length) {
return getLastMessage(msg.fwd_messages[0])
}
return msg
}
module.exports = getLastMessage
So I don't know much about botact but according to the code when you are hitting the / route, you need to pass a body containing an object property.
Now as this is bot framework for vk bots maybe it automatically sends the request body. You can make sure by logging the request body.
server.post('/', async (req,res)=>{
console.dir(req.body);
await bot.listen(req, res);
});
/lib/methods/listen.js:
const { type, secret, object, group_id } = framework === 'koa'
? args[0].request.body
: args[0].body
...
...
...
const { events, middlewares } = actions
const forwarded = this.getLastMessage(object)
Now, when you do bot.listen express passes req as first argument. and { type, secret, object, group_id } these fields get distructured from the req.body.
And then object is getting passed to the getLastMessage function.
So for the request body in minimum you would need
{ "object": {} }
Here is the 200 OK output that I got after added that to the request body from Postman
POC Code:
const express = require("express");
const bodyParser = require("body-parser");
const { Botact } = require("botact");
const server = express();
const bot = new Botact({
token: "token_for_my_group_i_just_hided_it",
confirmation: "code_for_my_group_i_just_hided_it"
});
server.use(bodyParser.json());
server.post("/",bot.listen);
server.listen(8080);
I have a clean url that contains some query param like this.
http://localhost:3000/post/:id
I'm trying to capture the query parameter 'id' on the client side like this.
static async getInitialProps({req, query: { id }}) {
return {
postId: id
}
}
render() {
const props = {
data: {
'id': this.props.postId // this query param is undefined
}
}
return (
<Custom {...props}>A component</Custom>
)
}
My express endpoint looks like this.
app.post(
'/post/:id',
(req, res, next) => {
let data = req.body;
console.log(data);
res.send('Ok');
}
);
But my server side console output ends up like this.
{ id: 'undefined' }
I've read the docs and the github issues but I can't seem to understand why this is happening.
Your frontend code is correct, fetching the post id from the query string is the way to go.
However your backend code is incorrect, first you need to use a GET route to render a Next.js page, and you must extract the path params to craft the final query params as a combination from both the regular query params as well as those path params, this could look like this using express:
const app = next({ dev: process.env.NODE_ENV === 'development' });
app.prepare().then(() => {
const server = express();
server.get('/post/:id', (req, res) => {
const queryParams = Object.assign({}, req.params, req.query);
// assuming /pages/posts is where your frontend code lives
app.render(req, res, '/posts', queryParams);
});
});
Check this Next.js example: https://github.com/zeit/next.js/tree/canary/examples/parameterized-routing for more info.