I'm writing Rest API in Node.JS which use MySQL database but also external API, where I need fetch some data.
I'm using Express and "Router, Middleware, Controller, Model" Architecture and I'n not sure what is the right solution to call an external API. In every request I'm sending token that is required for external API. I show what I have now and try describe the problem what I currently have (Read comments in code please.)
(Also if you have some articles or tutorials where is describe how right write Rest API in node thats uses Router, Middleware, Controller, Model architecture please let me know)
This is the main index.js
const express = require("express");
const dotenv = require('dotenv');
const cors = require("cors");
const HttpException = require('./utils/HttpException.utils');
const errorMiddleware = require('./middleware/error.middleware');
const userRouter = require('./routes/user.route');
const axios = require("axios");
// Init express
const app = express();
// Init environment
dotenv.config();
// parse requests of content-type: application/json
// parses incoming requests with JSON payloads
app.use(express.json());
// enabling cors for all requests by using cors middleware
app.use(cors());
// Enable pre-flight
app.options("*", cors());
const port = Number(process.env.PORT || 3331);
app.use(`/api/v1/users`, userRouter);
// This is an solution that works but I thinks is and nasty way how to do it
// You can see here how I need to call external API
app.get('/api/v1/test', (req, res, next) => {
const token = req.headers.token;
const respond = await axios.get(EXTERNAL_API_ENDPOINT, {
headers: {
cookie: `token=${token}`
}
});
});
// 404 error
app.all('*', (req, res, next) => {
const err = new HttpException(404, 'Endpoint Not Found');
next(err);
});
// Error middleware
app.use(errorMiddleware);
// starting the server
app.listen(port, () =>
console.log(`Server running on port ${port}!`));
module.exports = app;
user.route.js
const express = require('express');
const router = express.Router();
const userModel = require('../models/user.model');
const awaitHandlerFactory = require('../middleware/awaitHandlerFactory.middleware');
router.get('/currentUser', awaitHandlerFactory(userModel.getCurrentUser));
router.get('/logout');
module.exports = router;
I also have an Auth middleware to check token validation where i need to call external API which validate user.
Auth.middleware.js
const HttpException = require('../utils/HttpException.utils');
const UserModel = require('../models/user.model');
const dotenv = require('dotenv');
dotenv.config();
const auth = (roles) => {
return async function (req, res, next) {
try {
const token = req.headers.token;
if (!token) {
throw new HttpException(401, 'Access denied. No credentials sent!');
}
/* here I need call the external API and think that I should call it from
UserModal?, but I can't because Modal doesn't have req (should I sent it
in function parmas? like this?)*/
const user = await UserModel.getCurrentUser(token, params);
if (!user) {
throw new HttpException(401, 'Authentication failed!');
}
if(!user.active || user.active !== 'Y'){
throw new HttpException(401, `User ${user.userName} is not active!`);
}
// if the user role don't have the permission to do this action.
// the user will get this error
if (roles.length && !roles.includes(user.role)) {
throw new HttpException(401, 'Unauthorized');
}
// if the user has permissions
req.currentUser = user;
next();
} catch (e) {
e.status = 401;
next(e);
}
}
}
module.exports = auth;
I`m not sure how to handle this. I have to provide token and some data to call external API. Im not sure, if I shloud call Model or do it by Controller (or middleware?).
Where and how I should do it and why? Thanks!
Looks alright to me. Really depends on whether or not the API call needs to be made every request. Let say the user makes a request to your API, which then makes a request to an external API to authenticate that user, you're gonna need the auth token from every request.
You can organize your code a little better though. As example:
📦api
┣ 📂controllers
┃ ┗ 📜test.js
┣ 📂services
┃ ┗ 📜EXTERNAL_API_ENDPOINT.js
┗ 📜index.js
api/services/EXTERNAL_API_ENDPOINT.js
const axios = require("axios");
async function getService(token) {
return axios.get(EXTERNAL_API_ENDPOINT, {
headers: { cookie: `token=${token}` }
});
}
module.exports = getService;
api/controllers/test.js
const getService = require("../services/EXTERNAL_API_ENDPOINT");
async function test(req, res, next) {
const token = req.headers.token;
const respond = await getService(token)
}
module.exports = test;
api/index.js
const test = require("./controllers/test");
app.get('/api/v1/test', test);
Related
I am getting the above error message in my Postman while setting up a new POST request using my heroku url as initial and current values in Globals.
Postman returns 'Route does not exist' and when I check the Postman console I get "JSONError: Unexpected token 'R' at 1:1 Route does not exist ^"
Postman worked fine up until now I am trying to use heroku with it. The POST request still works fine while the url is local://host/api/v1 ... however with heroku url is gives back that error message.
I don't know what I am doing wrong and I have been stuck since last week, help will be appreciated. Thank you
THIS IS MY app.js CODE
require('dotenv').config();
require('express-async-errors');
//extra security packages
const helmet = require('helmet')
const cors = require('cors')
const xss = require('xss-clean')
const rateLimiter = require('express-rate-limit')
const express = require('express');
const app = express();
//connecting dB
const connectDB = require('./db/connect')
const authenticateUser = require('./middleware/authentication')
//import routes
const authRouter = require('./routes/auth')
const jobsRouter = require('./routes/jobs')
// error handler
const notFoundMiddleware = require('./middleware/not-found');
const errorHandlerMiddleware = require('./middleware/error-handler');
app.set('trust proxy', 1)
//built in mW
app.use(
rateLimiter({
windowMs: 15 * 60 * 1000, //15 minutes
max: 100, //limit each IP to 100 request per wMs
})
);
app.use(express.json());
app.use(helmet());
app.use(cors());
app.use(xss());
// routes - full path
app.use('/api/v1/auth', authRouter)
app.use('/api/v1/jobs', authenticateUser, jobsRouter) //authUser is added to this route to prevent unauthorized access to the Jobs route and prevent editing
app.use(notFoundMiddleware);
app.use(errorHandlerMiddleware);
const port = process.env.PORT || 5000;
const start = async () => {
try {
await connectDB(process.env.MONGO_URI)
app.listen(port, () =>
console.log(`Server is listening on port ${port}...`)
);
} catch (error) {
console.log(error);
}
};
start();
THIS is my auth code
const User = require('../models/User')
const {StatusCodes} = require('http-status-codes')
const { BadRequestError, UnauthenticatedError } = require('../errors')
const register = async (req,res) => {
const user = await User.create({...req.body})//'{...req.body}' enables mongoose to do the user validation
const token = user.createJWT()
res
.status(StatusCodes.CREATED)
.json({user:{name:user.name}, token})
}
const login = async (req,res) => {
const {email,password} = req.body
if(!email || !password){
throw new BadRequestError('Please provide email and password')
}
const user = await User.findOne({email})
if(!user){
throw new UnauthenticatedError('Invalid Credentials')
}
const isPasswordCorrect = await user.comparePassword(password)
if(!isPasswordCorrect){
throw new UnauthenticatedError('Invalid Credentials')
}
const token = user.createJWT()
res
.status(StatusCodes.OK)
.json({user:{name:user.name},token})
}
module.exports = {
register,
login,
}
THIS is my routes code
const express = require('express')
const router = express.Router()
const {login,register} = require('../controllers/auth')
router.post('/register', register)
router.post('/login', login)
module.exports = router
I finally solved the challenge. This is what I did ::
I generated a new SSH public key from my computer terminal which I then linked to my heroku settings.
I then deleted the old app I had created in my heroku, cleared all existing git repos within my app terminal and quit the app. Restarted my computer and then started the heroku create and deploy app process again from the beginning. Now everything works fine.
The problem was not having an SSH linking key.
Thank you #Chris for your support.
Right now I have a front end react application using axios and and a backend server using node.js and express. I cannot for the life of me get my serp api data to post so that my front end can get it through axios and display the json data. I know how to get data to the front end but I am not a backend developer so this is proving to be incredibly difficult at the moment. I'm able to get the data from the the external api, I just don't know how to post it once I get it. Also I would not like to have all these request running on server.js so I created a controller but I think that is where it is messing up. Any help is appreciated
//pictures controller
const SerpApi = require('google-search-results-nodejs');
const {json} = require("express");
const search = new SerpApi.GoogleSearch("674d023b72e91fcdf3da14c730387dcbdb611f548e094bfeab2fff5bd86493fe");
const handlePictures = async (req, res) => {
const params = {
q: "Coffee",
location: "Austin, Texas, United States",
hl: "en",
gl: "us",
google_domain: "google.com"
};
const callback = function(data) {
console.log(data);
return res.send(data);
};
// Show result as JSON
search.json(params, callback);
//res.end();
}
// the above code works. how do i then post it to the server so that i can retrieve it to the backend?
module.exports = {handlePictures};
//server.js
const express = require('express');
const app = express();
const path = require('path');
const cors = require('cors');
const corsOptions = require('./config/corsOptions');
const { logger } = require('./middleware/logEvents');
const errorHandler = require('./middleware/errorHandler');
const cookieParser = require('cookie-parser');
const credentials = require('./middleware/credentials');
const PORT = process.env.PORT || 3500;
// custom middleware logger
app.use(logger);
// Handle options credentials check - before CORS!
// and fetch cookies credentials requirement
app.use(credentials);
// Cross Origin Resource Sharing
app.use(cors(corsOptions));
// built-in middleware to handle urlencoded form data
app.use(express.urlencoded({ extended: false }));
// built-in middleware for json
app.use(express.json());
//middleware for cookies
app.use(cookieParser());
//serve static files
app.use('/', express.static(path.join(__dirname, '/public')));
// routes
app.use('/', require('./routes/root'));
app.use('/pictures', require('./routes/api/pictures'));
app.all('*', (req, res) => {
res.status(404);
if (req.accepts('html')) {
res.sendFile(path.join(__dirname, 'views', '404.html'));
} else if (req.accepts('json')) {
res.json({ "error": "404 Not Found" });
} else {
res.type('txt').send("404 Not Found");
}
});
app.use(errorHandler);
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
//api/pictures.js
const picturesController= require('../../controllers/picturesController');
const express = require('express')
const router = express.Router();
// for POST request use app.post
router.route('/')
.post( async (req, res) => {
// use the controller to request external API
const response = await picturesController.handlePictures()
// send the response back to client
res.json(response)
})
module.exports = router;
You just need to return the result from SerpApi in your handlePictures function. To do this make a new Promise and when search.json runs callback do what you need with the results and pass it in resolve.
Your picturesController.js with an example of returning all results.
//pictures controller
const SerpApi = require("google-search-results-nodejs");
const { json } = require("express");
const search = new SerpApi.GoogleSearch(process.env.API_KEY); //your API key from serpapi.com
const handlePictures = async (req, res) => {
return new Promise((resolve) => {
const params = {
q: "Coffee",
location: "Austin, Texas, United States",
hl: "en",
gl: "us",
google_domain: "google.com",
};
const callback = function(data) {
resolve(data);
};
search.json(params, callback);
});
};
module.exports = { handlePictures };
Output:
And I advise you to change your API key to SerpApi to prevent it from being used by outsiders.
Since I don't have the full context of your App I can just assume the context. But given the fact that you already have wrapped the logic of calling the external API into a dedicated controller you can use it in the following way in an express app (used the hello world example from express):
// import your controller here
const express = require('express')
const app = express()
const port = 3000
// for POST request use app.post
app.get('/', async (req, res) => {
// use the controller to request external API
const response = await yourController.method()
// send the response back to client
res.json(response)
})
app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
})
Here's an example how to execute the http request from the frontend:
const response = await fetch('http://localhost:3000') // result from res.json(response)
I'm new on node js, and the company that i work for needs a proof of concept about postgraphile, the situation is this:
I created a node js mini server that uses postgraphile to access the data on postgres
The mini server works fine and can return data and also can use mutations.
I used keycloak-connect to try to access keycloak to authenticate the token from the request that is sent by postman but there is a problem.
If the token is valid or not it does not matter for the mini server, the only thing that seems to matter is that is a bearer token.
I tried to use other plugins (like keycloak-nodejs-connect, keycloak-verify, etc) but the result is the same, i also changed my code to use the examples in the documentation of those plugins but nothing.
This is my code: (keycloak-config.js file)
var session = require('express-session');
var Keycloak = require('keycloak-connect');
let _keycloak;
var keycloakConfig = {
clientId: 'type credential',
bearerOnly: true,
serverUrl: 'our company server',
realm: 'the test realm',
grantType: "client_credentials",
credentials: {
secret: 'our secret'
}
};
function initKeycloak(){
if(_keycloak){
console.warn("Trying to init Keycloak again!");
return _keycloak;
}
else{
console.log("Initializing Keycloak...");
var memoryStore = new session.MemoryStore();
_keycloak = new Keycloak({store: memoryStore}, keycloakConfig);
return _keycloak;
}
}
function getKeycloak(){
if(!_keycloak){
console.error('Keycloak has not been initialized. Please called init first');
}
return _keycloak;
}
module.exports = {
initKeycloak,
getKeycloak
};
My Index.js file:
const express = require('express')
const bodyParser = require('body-parser')
const postgraphile = require('./postgraphile')
const app = express()
const keycloak = require('../config/keycloak-config').initKeycloak()
var router = express.Router();
app.set( 'trust proxy', true );
app.use(keycloak.middleware());
app.use(cors());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(postgraphile);
app.get('/', keycloak.checkSso(), (req, res) => {
res.send('success');
} );
var server = app.listen(8080, () => console.log(`Server running on port ${8080}`));
Also I used this code to get the token and use the keycloak-verify plugin but got nothing:
router.get('/',keycloak.protect(),function(req, res, next) {
var token=req.headers['authorization'];
console.log(token);
try {
let user = keycloak.jwt.verify(token);
console.log(user.isExpired());
} catch (error) {
console.error(error);
}
})
I know that I lack the knowledge because I am a backend (C#) developer, can somebody help me with this?, thanks in advance.
I found the answer to my problem:
const express = require("express");
const request = require("request");
var keycloakConfig = require('../AuthOnly/config/keycloak-config').keycloakConfig;
const postgraphile = require('./postgraphile');
const app = express();
const keycloakHost = keycloakConfig.serverUrl;
const realmName = keycloakConfig.realm;
// check each request for a valid bearer token
app.use((req, res, next) => {
// assumes bearer token is passed as an authorization header
if (req.headers.authorization) {
// configure the request to your keycloak server
const options = {
method: 'GET',
url: `${keycloakHost}/auth/realms/${realmName}/protocol/openid-connect/userinfo`,
headers: {
// add the token you received to the userinfo request, sent to keycloak
Authorization: req.headers.authorization,
},
};
// send a request to the userinfo endpoint on keycloak
request(options, (error, response, body) => {
if (error) throw new Error(error);
// if the request status isn't "OK", the token is invalid
if (response.statusCode !== 200) {
res.status(401).json({
error: `unauthorized`,
});
}
// the token is valid pass request onto your next function
else {
next();
}
});
} else {
// there is no token, don't process request further
res.status(401).json({
error: `unauthorized`,
});
}});
app.use(postgraphile);
app.listen(8080);
I create user service with login and register in node js. when i using authenticate middleware i got this kind of errors. if any one have solution. please let me know. i attached code and error image.
this is my route file.
const { Router} = require('express');
const authController = require('../controllers/authController');
const {authMiddleware} = require('../middleware/authMiddleware')
const router = Router();
router.get('/users',{authMiddleware}, authController.users_get);
router.post('/users',authController.users_post);
router.post('/authenticate',authController.authenticate_post);
module.exports = router;
this is my middleware file
const jwt = require('jsonwebtoken');
const requireAuth =(req, res, next)=>{
const token = req.cookie.jwt;
//check json web token exists & is verified
if(token){
jwt.verify(token,'vivekeviv',(err, decodedToken)=>{
if (err){
console.log(err)
}
else {
console.log(decodedToken);
next();
}
})
}
else {
console.log("You need to login")
}
}
module.exports ={requireAuth}
how to add middleware to this code.
i got this kind of error.
You are passing an object where express is expecting a function. You want:
const {requireAuth} = require('../middleware/authMiddleware')
...
router.get('/users', requireAuth, authController.users_get);
I have making an API using express and node.
Here is my app.js
const express = require('express');
const bodyParser = require('body-parser');
const dotenv = require('dotenv');
// setup dotenv to read environment variables
dotenv.config()
// Load Environment Varibles
const env = require('./utils/env');
// INIT MONGODB CONNECTION
require('./mongoose');
// create a new express application
const app = express();
// setup bodyparser middleware to read request body in requests
// we're only reading JSON inputs
app.use(bodyParser.json());
// Listen to API routes
const apiRoutes = require('./routes')
app.use('/api', apiRoutes);
// Start listening to requests
app.listen(env.PORT, () => {
console.log(`Server started on PORT ${env.PORT}`);
});
And here is the API routes that are being imported
const express = require('express');
const apiController = require('./apiController');
const apiValidator = require('./apiValidator');
const router = express.Router();
router.post('/login', apiValidator.loginUserValidator, apiController.loginUserController);
router.get('/rand', (req, res) => {
res.send('Some randon text');
});
module.exports = router;
Here is the middleware
const {
failureResponse
} = require('./../utils/response');
const errorcodes = require('./../utils/errorcodes');
const loginUserValidator = (req, res, next) => {
const user = req.body;
if (!user.username) {
return res.status(400).json(failureResponse(errorcodes.ERROR_INVALID_BODY_PARAMETER, "Invalid username"));
}
if (!user.password) {
return res.status(400).json(failureResponse(errorcodes.ERROR_INVALID_BODY_PARAMETER, "Invalid password"));
}
if (user.authTokens) {
delete user.authTokens;
}
next();
};
module.exports = {
loginUserValidator
};
Here is the controller
const User = require('./../models/user');
const {
successResponse,
failureResponse
} = require('./../utils/response');
const errorcodes = require('./../utils/errorcodes');
const loginUserController = async (req, res) => {
try {
const user = req.body;
// find if the user already exists
const existingUser = await User.findOne({
username: user.username
});
if (existingUser) {
// user exists. generate token and login user
console.log('Existing user login');
const token = existingUser.generateAuthToken();
return res.status(200).json(successResponse(token));
} else {
console.log('New user login');
const savedUser = await new User(user).save();
const token = savedUser.generateAuthToken();
return res.status(200).json(successResponse(token));
}
} catch (e) {
console.log(e);
return res.status(400).json(failureResponse(errorcodes.ERROR_SERVER_ERROR, "Unable to login user"));
}
};
module.exports = {
loginUserController
};
Here the issue is when I try to hit the login route from Postman, I am getting an error which says Could not get any response.
But when I hit the rand route, the output is correct.
So the issue isn't the arrangement of the code.
Why am I not able to use the login route here?