I have followed the node-postgres.org instruction at https://node-postgres.com/guides/async-express to connect via async/await to my postgres table users.
Navigating to localhost:3000/users/1 will return the JSON string for user 1 in the browser. I have extended this a bit to return all users at localhost:3000/users. My routes/user.js script is:
const Router = require('express-promise-router')
const db = require('../db')
// create a new express-promise-router
// this has the same API as the normal express router except
// it allows you to use async functions as route handlers
const router = new Router()
// export our router to be mounted by the parent application
module.exports = router
router.get('/:id', async (req, res) => {
console.log('Where id = ');
const { id } = req.params
const { rows } = await db.query('SELECT * FROM users WHERE id = $1', [id])
res.send(rows[0])
})
router.get('/', async (req, res) => {
console.log('*');
const { rows } = await db.all('SELECT * FROM users')
res.send(rows)
})
the index for this route at routes/index.js is simply:
const users = require('./user')
module.exports = (app) => {
app.use('/users', users)
}
and the db.query() and db.all() functions that I am awaiting are in db/index.js:
const { Pool } = require('pg')
const pool = new Pool()
module.exports = {
query: (text, params) => pool.query(text, params),
all: (text) => pool.query(text)
}
The routes are required in my main app.js file:
// ./app.js
const express = require('express')
const mountRoutes = require('./routes')
const cons = require('consolidate')
const path = require('path')
const bodyParser = require('body-parser')
const app = express()
mountRoutes(app)
// Assign Dust Engine to .dust files
app.engine('dust', cons.dust);
// Set .dust as the default extension
app.set('view engine', 'dust');
app.set('views', __dirname + '/views');
// Set Public Folder
app.use(express.static(path.join(__dirname, 'public')));
//Body parser and Middleware
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
extended: false
}));
app.get('/', function(reg, res) {
console.log('Root');
res.render('index', {hallo:'test'})
});
//Server
app.listen(3000, function() {
console.log('Server Started on Port 3000');
});
So far this works beautifully! I get the JSON strings that I want and I can build upon this sort of API by extending my routes and queries.
Question:
How can I return my JSON object rows back to app.js to res.render() it there?
or
Can I do something like this anywhere in my app:
jsonVar = GetMyJson('/users/1');
console.log(jsonVar);
returns:
[
{
"id": 1,
"usr_name": "Michael"
},
{
"id": 2,
"usr_name": "Thomas"
},
{
"id": 3,
"usr_name": "Paul"
}
]
I could then pass whatever route and parameters I want into GetMyJson() and deal with the resulting JSON.
This may be a trivial question for javascript devs ...
Thanks!
EDIT 21/12/2017
I have created a frontend script called graphs.js that simply logs my result when i call the fuction api('/user/1').
var el = document.getElementById("clickMe");
if (el.addEventListener)
el.addEventListener("click", api, false);
else if (el.attachEvent)
el.attachEvent('onclick', api);
var api = function(what){
// Action
sendRequest(what, function(result){
if(result){
log(result);
}
})
}
var apiEndpoint = 'http://localhost:3000/'
function sendRequest(_path, cb) {
var oReq = new XMLHttpRequest();
oReq.open('GET', apiEndpoint+_path);
oReq.onreadystatechange = function() {
if (this.readyState === 4) {
cb(JSON.parse(this.response));
}
else{
cb(null);
}
}
oReq.send();
}
function log(msg){
console.log(msg);
}
BUT
Is that a proper way of doing it in javascript?
The way to go about it would be something like this:
router.get('/', async (req, res) => {
console.log('*');
const { rows } = await db.all('SELECT * FROM users')
res.render('user', rows)
});
Instead of res.send you do res.render
You have to get it from database. Making self call will not be an ideal solution. So you can do following
const db = require('../db') // fix the location of db.js
app.get('/', function(req, res) {
db.all('SELECT * FROM users').then(function({ rows }) {
res.render('index', rows)
})
})
Best would be to make an userRepository.js and add code related to db over there. Then use that repository at both places.
Related
I´m making an API with node and express that fetches data from an SQLite database and sends as a response. My database is structured as follows:
I have a set of tables, one for each customer. These tables contain a set of rows, one for each page the customer has.
For example say that I have two customers; Fruit and Vegetables. The dream scenario would be that localhost:8080/fruit leads to a home page for Fruit, and that localhost:8080/fruit/apples leads to the apples-page for fruit, without me having to manually program all the specific routes. I know about the :id parameter in express, and the functionality I would like is basically using the :id twice, like localhost:8080/:id1/:id2.
Here´s my code:
import express from "express";
const app = express();
const PORT = 8080;
app.listen(PORT, () => console.log(`It's alive on http://localhost:${PORT}`));
app.use(express.json());
app.use(express.static("start"));
app.use(express.urlencoded({ extended: true }));
app.set("view engine", "ejs");
//* These is the router in use
import router from "../controller/router.js";
// * This defines what URI is used for the router
app.use("/", router);
import express from "express";
import { fetchContent } from "../model/model.js";
const router = express.Router();
//* Sends out the ejs (basically HTML) on start URL "localhost:8080"
router.get("/", function (req, res) {
converter();
res.render("../pages/start.ejs");
});
//* Querys the database, the table ":id"
router.get("/:id", async function (req, res, next) {
let id = req.params.id;
let sqlQuery = `SELECT page_content FROM ${id} WHERE page_name = "about";`;
const content = await fetchContent(sqlQuery);
if (content == "404") {
res.status(404).render("../pages/404.ejs");
} else {
res.status(200).send(JSON.parse(content));
next();
}
});
export default router;
import sqlite3 from "sqlite3";
const db = new sqlite3.Database(
"path/to/my/db.db"
);
export function fetchContent(sqlQuery) {
return new Promise((resolve, reject) => {
db.all(sqlQuery, [], (err, rows) => {
if (err) {
resolve("404");
} else {
rows.forEach((row) => {
resolve(row.page_content);
});
}
});
});
}
I have made this work with one :id, and the API fetches whatever table the :id is, but right now it´s a predetermined page, and I can´t go anywhere from there. If my explanation of what I would like was hard to follow just ask and I´ll try to explain better. :D
This was actually really simple. All I had to do was to make a new parameter. On my old code I had this:
router.get("/:id", async function (req, res, next) {
let customerId = req.params.id;
let sqlQuery = `SELECT page_content FROM ${id} WHERE page_name = "about";`;
});
But now I renamed the old one and made a new one, and now my code looks like this:
router.get("/:customerId", async function (req, res, next) {
let customerId = req.params.customerId;
let sqlQuery = `SELECT page_content FROM ${customerId} WHERE page_name = "about";`;
});
router.get("/:customerId/:pageId", async function (req, res, next) {
let customerId = req.params.customerId;
let pageId = req.params.pageId;
let sqlQuery = `SELECT page_content FROM ${customerId} WHERE page_name = "${pageId}";`;
});
I am making a Portfolio application with nodeJS and express. I want to implement an admin panel which I can create,delete, update and edit my skills,experience,about etc, but I don't know how can I keep those admin routes secret and what kind of authentication to make.If we can do by putting Basic authentication on post,patch,delete route then how will we implement basic authentication on routes.
index.js
const express = require('express');
const app = express();
var cors = require('cors');
require('./db/mongoose')
const menuRouter = require('./routers/menu')
const skillRouter = require('./routers/skill')
const aboutRouter = require('./routers/About')
const experienceRouter = require('./routers/Experience')
const resumerouter = require('./routers/Resume')
const userRouter = require('./routers/user')
const port = process.env.PORT || 4000;
app.use(express.json());
app.use(cors());
app.use(menuRouter);
app.use(skillRouter);
app.use(aboutRouter);
app.use(experienceRouter);
app.use(resumerouter);
app.use(userRouter)
app.listen(port, () => {
console.log("Server is runing on port" + port)
});
skill.js
const express = require('express');
const Skill = require('../model/skill');
const router = new express.Router();
router.post('/skill', async (req, res) => {
const skill = new Skill(req.body);
try {
await skill.save();
res.status(201).send(skill);
} catch (e) {
console.log(e);
res.status(400).send(e);
}
})
router.get('/skill', async (rq, res) => {
try {
const skill = await Skill.find({});
res.status(201).send(skill);
} catch (e) {
res.status(400).send(e);
}
})
module.exports = router;
As specified in the comments, I would refactor your code a bit, seems messy and you're kind'a repeating yourself every line you import a route, so, you should do it better as well...
have an index.js file in your /routers folder with the content of the demo repo I've made for other StackOverflow question
then, to separate things, I would do something like:
const routes = require('./routes')
...
const protectRouteWithApiKey = (req, res, next) => {
const auth = req.headers['x-apikey']
if (auth && auth === '<YOUR API KEY>') return next()
return next(new Error('403 | Authorization is missing or value is wrong'))
}
...
app.use('/api', protectRouteWithApiKey, routes) // point to your routes and protect access
app.use('/', defaultEngine) // your engine to render html
you would then have a protected route in /api/* and normal routes for everything else
A middleware where you detect if the logged user is the admin?
In this sample checking by the email, and you can save the adminemail as a global variable
ensureAdmin: function(req, res, next) {
if (req.isAuthenticated()) {
if (req.user.email === adminemail) {
return next();
} else {
return res.redirect('/adminsecretroute');
}
}
res.redirect('/');
}
I have node-express app where I have bunch of Routes for login, logout and signup and one Route for checking authorised Route which can be accessed only through providing authToken. I moved the Routes to separate Route file and I got the above error.
This is my Users Routes File:
const express = require('express');
const authenticate = require('./../middleware/authenticate');
const router = express.Router();
const {User} = require('./../models/user');
router.post('/',(req, res) => {
var body = _.pick(req.body,['email','password']);
var user = new User(body);
user.save().then(() => {
return user.generateAuthToken()
}).then((token) => {
res.header('x-auth', token).send(user);
}).catch((e) => {
res.status(400).send(e);
});
});
router.post('/login',(req, res) => {
var body = _.pick(req.body, ['email', 'password']);
User.findByCredentials(body.email, body.password).then((user) => {
return user.generateAuthToken().then((token) => {
res.header('x-auth', token).send(user);
});
}).catch((e) => {
res.status(400).send(e);
});
});
router.delete('/logout',authenticate, (req, res) => {
req.user.removeToken(req.token).then(() => {
res.status(200).send();
},(e) => {
res.status(400).send(e);
}) ;
});
router.get('/me',authenticate, (req,res) => {
res.send(req.user);
});
module.exports = router;
Following is my main server.js file:
const express = require('express');
const _ = require('lodash');
var app = express();
const usersRoutes = require('./routes/users');
app.use(express.json());
app.use('/users', usersRoutes);
var {mongoose} = require('./db/mongoose');
var {User} = require('./models/user');
var {authenticate} = require('./middleware/authenticate');
const port = process.env.PORT || 3000 ;
app.listen(port, () => console.log(`Listening on ${port}...`))
I have a model/Schema(mongoose) file for User so If You feel you need that I am ready to edit my question. Thanks.
The problem is that router.delete is expecting a function on the middleware parameter (like you did in your server.js file with app.use(express.json())) so it can be used like a callback which gets called whenever a request reach your route.
Try changing authenticate to authenticate().
It seems like in your users routes file you are importing the entire module who contains the authenticate function, so when try to access it like a function you'll get an error. You need to import it like you did in your server.js file.
Change the line const authenticate = require('./../middleware/authenticate'); for const {authenticate} = require('./../middleware/authenticate');.
I get a message Request is not finished yet and no data will be sent, if I do patch and delete requests in my app ( the post and get request work well)
Here are my requests
In service (front, Angular 4) I create the requests
api = 'http://localhost:3000/tasks';
deleteData(id) {
return this.http.delete( this.api, id);
}
patch(data) {
return this.http.patch( this.api, data);
}
And then call them in component
this.deleteData(this.checkedItems);
this.service.patch(result.data).subscribe(d => {
this.tasks = d;
});
The service
The PATCH request get req.body via console.log - so it should works, but it doesn't
The DELETE request doesn't get any data! The req.body is empty! But I need to pass the array of ids, so I can't do it via params.
Could you please help me or give a hint? Here is my service
const express = require('express');
const path = require('path');
const http = require('http');
const bodyParser = require('body-parser');
const mongoose = require('mongoose');
const cors = require('cors');
var Schema = mongoose.Schema;
const app = express();
//Middleware for CORS
app.use(cors());
app.use(express.json());
// Parsers for POST data
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
// Point static path to dist
app.use(express.static(path.join(__dirname, 'dist')));
var todoSchema = new Schema({
taskName: String,
createdAt: Date,
isDone: Boolean,
prioraty: String
}, {
collection: 'tasks'
});
var Model = mongoose.model('Model', todoSchema);
//replace when DB is online
mongoose.connect('mongodb://localhost:27017/admin').then(() => {
console.log("connected");
}).catch (() => {
console.log("not connected");
});
mongoose.connection.once('open', function () {
console.log('mongodb connected.');
});
app.patch('/tasks', function (req, res) {
console.log(req.body);
var updateObject = {
'taskName': req.body.taskName,
'isDone': req.body.isDone,
'prioraty': req.body.prioraty
}
var id = req.body._id;
Model.collection.update({_id : id}, {$set: updateObject});
});
app.delete('/tasks', function(req,res){
console.log('Delete', req.body);
var ids = [];
for (let i = 0; i < req.body.length; i ++) {
ids.push(req.body[i]._id);
}
var myquery = { _id: { $in: ids } };
Model.collection.deleteMany(myquery, function(err, obj) {
if (err) throw err;
});
});
const port = process.env.PORT || '3000';
app.set('port', port);
/**
* Create HTTP server.
*/
const server = http.createServer(app);
/**
* Listen on provided port, on all network interfaces.
*/
server.listen(port, () => console.log(`API running on localhost:${port}`));
You need to close the connection when you're done handling the request, otherwise the client will wait for the server to send a response until the request timeout is reached.
app.patch('/tasks', function (req, res) {
...
Model.collection.update({_id : id}, {$set: updateObject}, function (err) {
if (err) {
console.error(err);
return res.sendStatus(500);
}
res.sendStatus(200);
});
});
app.delete('/tasks', function(req,res){
...
Model.collection.deleteMany(myquery, function(err) {
if (err) {
console.error(err);
return res.sendStatus(500);
}
res.sendStatus(200);
});
});
As for the DELETE request not having a req.body, that's because Angular 4's http client doesn't allow a body for DELETE requests. Its API for DELETE requests looks like this: this.http.delete(url, httpOptions), with no body support. You'll have to use query parameters if you need to send an array of ids. Query params does support arrays, they look something like this: https://myurl.xyz/tasks?ids[]=1&ids[]=2&ids[]=3
See https://angular.io/guide/http#url-parameters
I'm trying to route different radio sessions/episodes from database queries with express.js. I'm able to grab the route param from the URL in my express route. But I'm unsure how to use the param to query the database for the right object.
I'm able to get this to work by hardcoding the param into my service as shown below with the 'title' variable in service.js. Currently, I only know how to pass variables from express to the view. Is there a way to pass the title param from my express route to the service.js file?
Routes.js
'use strict';
const express = require('express');
const Sessions = require('../models/sessions')
const routes = express.Router();
const apiRoutes = express.Router();
apiRoutes.get('/sessions', (req, res) => {
Sessions.find({}, (err, sessions) => {
if(err) {
return res.status(500).json({message: err.message});
}
res.json({sessions: sessions});
})
});
routes.get('/', (req, res, next) => {
return res.render('index', {title: 'Home'});
});
// I added this bit of code after realizing I needed to use mongoose to query the database. I'm still unsure how to pass the object to my javascript but I'm getting closer.
routes.get('/:title', function (req, res, next) {
const title = req.params.title
Sessions.find((err, sessions) => {
if (err) return console.error(err);
for (let i = 0; i < sessions.length; i++) {
if (title === sessions[i].title.toLowerCase()) {
res.render('session')
}
}
})
})
module.exports = [routes, apiRoutes];
index.js
'use strict';
const express = require('express');
const app = express();
const [routes, apiRoutes] = require('./src/routes');
require('./src/database');
require('./src/seed');
app.use('/', express.static('public'));
app.set('view engine', 'pug');
app.set('views', __dirname + '/views');
app.use('/api', apiRoutes);
app.use('/', routes);
app.listen(process.env.PORT || 8082);
service.js
const root = window.location.origin;
const title = 'session 1'
function callService(){
service(`${root}/api/sessions`)
.then(retrieveSession)
.catch(function(e) {
console.log(e)
});
}
function retrieveSession(data) {
for (let i = 0; i < data.sessions.length; i++) {
if(data.sessions[i].title.toLowerCase() === title) {
return data.sessions[i];
}
}
}
function service(url) {
return new Promise(function(res, rej) {
var httpRequest = new XMLHttpRequest();
httpRequest.open('GET', url);
httpRequest.onreadystatechange = handleResponse;
httpRequest.onerror = function(error) {
rej(error)
}
httpRequest.send();
function handleResponse() {
if (httpRequest.readyState === 4) {
if (httpRequest.status === 200) {
var data = JSON.parse(httpRequest.responseText);
res(data)
} else {
rej(this.statusText)
}
}
};
});
}
I was able to achieve the desired result by passing the data to the view via mongoose with the following snippet.
routes.get('/:title', function (req, res, next) { // Passes url param
const title = req.params.title //stores url param in varaible
Sessions.findOne({ 'title': title }, function (err, session) { // compares url param against my mongoose schema and queries the database for the desired object
if (err) return handleError(err); // checks for errors
res.render('session', {session: session}); // renders the page while passing the desired data.
})
})