How to handle a 404 in Koa 2? - javascript

I have a 404.jade file that I want to render whenever there is an invalid GET request.
Here is my current code:
app.js
import Koa from 'koa'
import views from 'koa-views'
import serve from 'koa-static'
import rootRoutes from './routes/index'
import userRoutes from './routes/user'
const app = new Koa()
app.use(views(`${__dirname}/views`, { extension: 'jade' }))
app.use(serve(`${__dirname}/public`))
app.use(rootRoutes.routes())
app.use(userRoutes.routes())
app.listen(3000, () => {
console.log('Server running at http://localhost:3000')
})
export default app
routes/index.js
import Router from 'koa-router'
const router = new Router()
router.get('/', async ctx => {
await ctx.render('index')
})
router.get('/about', async ctx => {
await ctx.render('about')
})
export default router
routes/user.js
import Router from 'koa-router'
const router = new Router({ prefix: '/user' })
router.get('/:name', async ctx => {
const user = ctx.params.name
await ctx.render('user', { user })
})
export default router
How can I handle any type of invalid GET request and somehow use await ctx.render('404') whenever it happens?

You can add a custom middleware in your app.js file.
import Koa from 'koa'
import views from 'koa-views'
import serve from 'koa-static'
import rootRoutes from './routes/index'
import userRoutes from './routes/user'
const app = new Koa()
app.use(async(ctx, next) => {
try {
await next()
const status = ctx.status || 404
if (status === 404) {
ctx.throw(404)
}
} catch (err) {
ctx.status = err.status || 500
if (ctx.status === 404) {
//Your 404.jade
await ctx.render('404')
} else {
//other_error jade
await ctx.render('other_error')
}
}
})
app.use(views(`${__dirname}/views`, { extension: 'jade' }))
app.use(serve(`${__dirname}/public`))
app.use(rootRoutes.routes())
app.use(userRoutes.routes())
app.listen(3000, () => {
console.log('Server running at http://localhost:3000')
})
export default app

The default value of ctx.response.status is 404
application.js line 125 :
callback() {
const fn = compose(this.middleware);
if (!this.listeners('error').length) this.on('error', this.onerror);
const handleRequest = (req, res) => {
res.statusCode = 404; // defaul
const ctx = this.createContext(req, res);
const onerror = err => ctx.onerror(err);
const handleResponse = () => respond(ctx);
onFinished(res, onerror);
return fn(ctx).then(handleResponse).catch(onerror);
};
return handleRequest;
}
and if you call :
this.render('index',{});
this.send();
this.body='';
the status code will change automatically.
So we can just use this :
app.use(async (ctx, next) => {
if(parseInt(ctx.status) === 404){
ctx.status = 404
ctx.body = {msg:'emmmmmmm, seems 404'};
}
})
Warning here, if you are using koa-router, make sure the function above is called with app.use( app = new Koa() ), not router.use

Related

React axios.get 401 unauthorized

I used the postman for testing my url,and it has response data(It's correct).
Url:http://localhost:8000/api/products/find/6337d5d73ec2c05d7c11bc63(ProductId).
In postman,it could get response data.
I wonder I do give it Bearer token in my requestMethods.js,which imported in /pages/Product.jsx.
But when I use the same id in axios.get,it said error:
Error:GET http://localhost:8000/api/products/find/6337d5d73ec2c05d7c11bc63 401 (Unauthorized)
can someone just help me solve this question?
In /pages/product.jsx line 145,157 are got tag in the photo.
Here are my relative Codes:
requestMethods.js
import axios from "axios";
const BASE_URL = "http://localhost:8000/api/"
const TOKEN = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9......"
export const publicRequest = axios.create({
baseURL: BASE_URL,
});
export const userRequest = axios.create({
baseURL: BASE_URL,
headers: { token: `Bearer ${TOKEN}` },
});
/pages/Product.jsx
It's mainly codes of website.
import { useLocation } from 'react-router-dom'
import { useState, useEffect } from 'react';
import { publicRequest,userRequest } from './requestMethods';
const Product = () => {
// 回傳路徑
const location = useLocation();
const id = location.pathname.split("/")[2];
const [product, setProduct] = useState({});
useEffect(() => {
const getProduct = async () => {
try {
const res = await publicRequest.get("/products/find/" + id);//HERE USE REQUESTMETHODS.JS and it's it sad line:145
console.log(res.data)
setProduct(res.data);
}
catch { } }
getProduct();//line 157
}, [id])
}
export default Product
index.js
const express = require("express");
const app = express();
const mongoose = require("mongoose");
const dotenv = require("dotenv");
const userRouter = require("./routes/user");
const authRouter = require("./routes/auth");
const productRouter = require("./routes/product");
const cors=require("cors");
dotenv.config();
mongoose.connect(
process.env.MONGO_URL)
.then(() => console.log("connect"))
.catch(((err) => {
console.log(err);
}));
app.use(express.json());
app.use(cors());
app.use("/api/users", userRouter);
app.use("/api/auth", authRouter);
app.use("/api/products", productRouter);//USE THE ROUTES/PRODUCT.JS
app.use("/api/orders", orderRouter);
app.use("/api/cart", cartRouter);
app.use("/api/checkout",stripeRouter)
app.listen(process.env.PORT || 8000, () => {
console.log("Run server")
})
routes/product.js
router.put("/:id", verifyTokenAndAdmin, async (req, res) => {
try {
const updatedProduct = await Products.findByIdAndUpdate(req.params.id, {
$set: req.body
}, { new: true }
);
res.status(200).json(updatedProduct);
} catch (err) {
res.status(500).json(err);
}
});
//delete
router.delete("/:id", verifyTokenAndAdmin, async (req, res) => {
try {
await Products.findByIdAndDelete(req.params.id);
res.status(200).json("Products has been deleted.")
} catch (err) {
res.status(500).json(err);
}
})
//get product
router.get("/find/:id", verifyTokenAndAdmin, async (req, res) => {
try {
const product = await Products.findById(req.params.id);
res.status(200).json(product);
} catch (err) {
res.status(500).json(err);
}
})
verifyToken.js
const jwt = require("jsonwebtoken");
const verifyToken = (req, res, next) => {
const authHeader = req.headers.token;
if (authHeader) {
const token = authHeader.split(" ")[1];
jwt.verify(token, process.env.JWT_SEC, (err, user) => {
if (err)
res.status(401).json("Token is not valid!")
req.user = user;
next();
})
}
else {
return res.status(401).json("You are not authentication!" );
}
}
module.exports = { verifyToken, verifyTokenAndAuth, verifyTokenAndAdmin };
The reason is you are using publicRequest axios function which does not include bearer token header. Use userRequest function.
Try using the code below in your axios request from frontend:
const res = await userRequest.get("/products/find/${id}");
Instead of double quotes use string literal. Stack overflow is using my string literal as code snippet that's why I wrote in double quotes.
Also remove the extra slash after api from BASE_URL like this:
const BASE_URL = "http://localhost:8000/api"
Also check in your verifyToken.js file if you have written the functions verifyTokenAndAuth and verifyTokenAndAdmin which you are exporting. If not then please write those functions in verifyToken.js file and then export because you are using verifyTokenAndAdmin middleware in your api. Also please check that server is running and not crashing before making a request.

Express.js, getting 404 (Not Found) error for my newly added route

I am having some issues while creating second route on my project. While router.get("/getallrooms") works just fine, router.post("/getroombyid") is giving me 404 not found message:
POST http://localhost:3000/book/api/rooms/getroombyid 404 (Not Found)
I have attached my routes code, Booking screen, where I am trying to use this getroombyid route and the server.js code. I will gladly accept any suggestions.
const express = require('express');
const router = express.Router();
const Room = require('../models/room')
router.get("/getallrooms", async(req, res) =>{
try{
const rooms = await Room.find({})
res.send(rooms)
}
catch(error){
return res.status(400).json({message: error});
}
});
router.post("/getroombyid", async(req, res) => {
const roomid = req.body.roomid
try {
const room = await Room.findOne({'_id' : req.body.roomid})
res.send(room)
} catch (error) {
return res.status(400).json({ message: error });
}
});
module.exports = router
Bookingscreen
import React, {useState, useEffect} from 'react'
import axios from 'axios'
function Bookingscreen({match}) {
const [loading, setloading] = useState()
const [error, seterror] = useState()
const [room, setroom] = useState()
useEffect(() => {
try {
setloading(true)
async function gettingRoom() {
const data = (await axios.post('./api/rooms/getroombyid', {roomid : match.params.roomid})).data
setroom(data)
setloading(false)
}
gettingRoom()
}
catch (error) {
seterror(true)
console.log(error)
setloading(false)
}
}, [])
return (
<div>
<h1>Bookingscreen</h1>
<h1>Room id = {match.params.roomid}</h1>
</div>
)
}
export default Bookingscreen
server.js
const express = require("express");
const app = express();
const dbConfig = require('./db')
const roomsRoute = require('./routes/roomsRoute')
app.use(express.json())
app.use('/api/rooms', roomsRoute)
const port = process.env.PORT || 5000;
app.listen(port, () => {
console.log(`App listening on port ${port}`)
});
The problem is caused by the below line, specifically the ./api part:
const data = (await axios.post('./api/rooms/getroombyid', {roomid : match.params.roomid})).data
What you are doing is taking into account your current url inside the browser, which I assume is /book, so the final request is made to:
http://localhost:3000/book/api/rooms/getroombyid
Which doesn't exist on your back end. Change the above line with this one:
const data = (await axios.post('/api/rooms/getroombyid', {roomid : match.params.roomid})).data

Function anonymous when running backend server

I'm trying to create a routing function for some products. The home screen works fine (displaying all the products), but I'm trying to add functionality so that when a user clicks on the product it automatically redirects to a page which pulls information about that products.
When I run my back end server (npm start) I get this in my terminal and an undefined response when I click on my product:
[nodemon] 2.0.12
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): backend/**/*
[nodemon] watching extensions: js,mjs,json
[nodemon] starting `babel-node backend/server.js`
serve at http://localhost:5000 [Function (anonymous)]
I've tried going through my code a few times and playing around with things to see if there's an issue with a function but couldn't see an issue.
This is the code for my server.js:
import express from 'express';
import cors from 'cors';
import { products } from './data.js';
const app = express();
app.use(cors());
app.get('/api/products', (_req, res) => {
res.send(products);
});
app.get('/api/products/:id')
app.listen(5000, () => {
console.log('serve at http://localhost:5000', (req, res) => {
const product = data.products.find((x) => x._id === req.params.id)
if (product) {
res.send(product);
} else {
res.status(404).send({ message: 'Product Not Found' })
}
})
});
This is my config.js:
import axios from 'axios'
import { apiUrl } from "./config"
export const getProduct = async (id) => {
try {
const response = await axios({
url: `${apiUrl}/api/products/${id}`,
method: 'GET',
headers: {
'Content-Type': 'application/json',
}
});
if (response.statusText !== 'OK') {
throw new Error(response.data.message);
}
return response.data;
} catch (err) {
console.log(err);
return { error: err.message }
}
};
I've just included my util.js and the product screen code below just for a better understanding of what I'm doing, however I think the issue would be in the server.js or config.js.
util.js:
export const parseRequestUrl = () => {
const url = document.location.hash.toLowerCase();
const request = url.split('/');
return {
resource: request[1],
id: request[2],
action: [3],
};
}
productscreen.js
import { getProduct } from '../api';
import { parseRequestUrl } from '../util'
const ProductScreen = {
render: async () => {
const request = parseRequestUrl();
const product = await getProduct(request.id);
return `<h1>${product.name}</h1>`;
}
}
export default ProductScreen;
You are literally printing the function with console.log by providing it as a second argument. Move the handler function to the appropriate route handler for /api/products/:id, as shown below:
import express from 'express';
import cors from 'cors';
import { products } from './data.js';
const app = express();
app.use(cors());
app.get('/api/products', (_req, res) => {
res.send(products);
});
// The handler function is moved here from `listen` callback.
app.get('/api/products/:id', (req, res) => {
const product = data.products.find((x) => x._id === req.params.id);
if (product) {
res.send(product);
} else {
res.status(404).send({ message: 'Product Not Found' });
}
})
app.listen(5000, () => {
console.log('serve at http://localhost:5000');
});

How can I test express server with supertest in next.js?

I have built my portfolio webpage with next.js now I need to test it. to test the express server I use supertest. But the problem is I need to refactor express to use it. Because supertest need to access to app() before listening.
I started the way how I used to implement in node.js app. Put the express code in app.js and call it in index.js.
const express = require("express");
const server = express();
const authService = require("./services/auth");
const bodyParser = require("body-parser");
//put all the middlewares here
module.exports = server;
and then in index.js
const server = require("express")();
// const { parse } = require("url");
const next = require("next");
const routes = require("../routes");
const path = require("path");
require("./mongodb");
const dev = process.env.NODE_ENV !== "production";
const app = next({ dev });
// const handle = app.getRequestHandler(); //this is built in next route handler
const handle = routes.getRequestHandler(app);
app
.prepare()
.then(() => {
const server = require("./app");
//I required this outside too but it did not solve the issue
server.listen(3000, (err) => {
if (err) throw err;
console.log("> Ready on http://localhost:3000");
});
})
.catch((ex) => {
console.error(ex.stack);
process.exit(1);
});
with this set up, express is listening, I am able connect to mongodb, during the start up there is no issue.
When i request to localhost:3000, there is no response from localhost, it is spinning till timeout
Create a test client:
// test-client.ts
import { createServer, RequestListener } from "http";
import { NextApiHandler } from "next";
import { apiResolver } from "next/dist/next-server/server/api-utils";
import request from "supertest";
export const testClient = (handler: NextApiHandler) => {
const listener: RequestListener = (req, res) => {
return apiResolver(
req,
res,
undefined,
handler,
{
previewModeEncryptionKey: "",
previewModeId: "",
previewModeSigningKey: "",
},
false
);
};
return request(createServer(listener));
};
Test your APIs with:
// user.test.ts
import viewerApiHandler from "../api/user";
import { testClient } from "../utils/test-client";
const request = testClient(viewerApiHandler);
describe("/user", () => {
it("should return current user", async () => {
const res = await request.get("/user");
expect(res.status).toBe(200);
expect(res.body).toStrictEqual({ name: "Jane Doe" });
});
});
For those who want to add query parameters, here's the answer:
import { createServer, RequestListener } from 'http'
import { NextApiHandler } from 'next'
import { apiResolver } from 'next/dist/server/api-utils/node'
import request from 'supertest'
export const handlerRequester = (handler: NextApiHandler) => {
const listener: RequestListener = (req, res) => {
let query = {}
let queryUrl = req.url.split('?')[1]
if (queryUrl) {
queryUrl
.split('&')
.map((p) => [p.split('=')[0], p.split('=')[1]])
.forEach((k) => {
query[k[0]] = k[1]
})
}
return apiResolver(
req,
res,
query,
handler,
{
previewModeEncryptionKey: '',
previewModeId: '',
previewModeSigningKey: '',
},
false
)
}
const server = createServer(listener)
return [request(server), server]
}
I've just released a new npm package which handle this case here:
https://www.npmjs.com/package/nextjs-http-supertest
Feel free to test it and give me feedback !

I keep getting http POST http://localhost:3000/api/authenticate 404 (Not Found)

am using angular 6 and express when am developing this api on authentcate uri it returning Http failure response for http://localhost:3000/api/authenticate: 404 Not Found
i have tried removing of the responses on my user.controller.js but the problem persisits it seems am missing out some point here and i dont know here it is at first i got an error saaying cant send headers after they are sent and the error was on my user.controller.js on this line else return res.status(404).json(info);
Here is my user.controller.js
const mongoose = require('mongoose');
const User = mongoose.model('User');
const passport = require('passport');
const _ = require('lodash');
module.exports.register = (req,res, next) => {
const user = new User();
user.fullname = req.body.fullname;
user.email = req.body.email;
user.College = req.body.College;
user.Department = req.body.Department;
user.password = req.body.password;
user.admintype = req.body.admintype;
user.save((err, doc) => {
if(!err) { res.send(doc)}
else
{
if(err.code == 11000)
res.status(422).send(['Duplicate email Address Found.'])
else
return next(err);
}
})
}
module.exports.authenticate = (req, res, next ) => {
//calll for passport authentication
passport.authenticate('local', (err, user, info) => {
//error form paasport middleware
if(err) return res.status(400).json(err);
//registered user
else if (user) return res.status(200).json({ "token":user.generateJwt() });
//unknown user or wrong password
else return res.status(404).json(info);
})(req, res);
}
module.exports.userProfile = (req, res, next) =>{
User.findOne({ _id:req._id},
(err,user) =>{
if(!user)
return res.status(404).json({ status: false, message : 'User Record not Found. '});
else
return res.status(200).json({ status:true , user : _.pick(user, ['fullname','email','university','College','Department','admintype'])});
} );
}
Here is my user.service.ts
```import { Injectable } from '#angular/core';
import { User } from './user.model';
import{ HttpClient, HttpHeaders } from '#angular/common/http';
import{ environment } from '../../environments/environment';
import { from } from 'rxjs';
#Injectable({
providedIn: 'root'
})
export class UserService {
selectedUser: User = {
fullname:'',
email:'',
university:'',
College:'',
Department:'',
password:'',
admintype:''
};
constructor(private http: HttpClient) { }
postUser(user:User)
{
return this.http.post(environment.apiBaseUrl+ '/register' ,user)
}
login(authCredentials)
{
return this.http.post(environment.apiBaseUrl+ '/authenticate',authCredentials);
}
setToken(token:string)
{
localStorage.setItem('token',token);
}
}```
Here is my sign-in.components.ts
```import { Component, OnInit } from '#angular/core';
import { NgForm } from '#angular/forms';
import { UserService } from 'src/app/shared/user.service';
import { Router } from '#angular/router';
#Component({
selector: 'app-sign-in',
templateUrl: './sign-in.component.html',
styleUrls: ['./sign-in.component.css']
})
export class SignInComponent implements OnInit {
constructor( private userService:UserService, private router:Router) { }
model = {
email:'',
password:''
};
emailRegex = /^(([^<>()\[\]\\.,;:\s#"]+(\.[^<>()\[\]\\.,;:\s#"]+)*)|(".+"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
serverErrorMessages : string;
ngOnInit() {
}
onSubmit(form :NgForm)
{
this.userService.login(form.value).subscribe(
res =>{
this.userService.setToken(res['token']);
this.router.navigateByUrl('/signup');
},
err =>{
this.serverErrorMessages = err.message;
});
}
}```
Here is my environment.ts
```/ This file can be replaced during build by using the `fileReplacements` array.
// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
// The list of file replacements can be found in `angular.json`.
export const environment = {
production: false,
apiBaseUrl:'http://localhost:3000/api'
};
/*
* For easier debugging in development mode, you can import the following file
* to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
*
* This import should be commented out in production mode because it will have a negative impact
* on performance if an error is thrown.
*/
// import 'zone.js/dist/zone-error'; // Included with Angular CLI.```
Here is my auth.js
```const router = require('express').Router();
const User = require('../controller/model/User');
const ctrlUser = require('../controller/user.controller');
const jwthelper = require('../jwtHelper')
//validation
router.post('/register', ctrlUser.register);
router.post('/authenticate',ctrlUser.authenticate);
router.get('/userProfile',jwthelper.verifyJwtToken,ctrlUser.userProfile);
module.exports = router;```
Here is my index.js
```const express = require('express');
const app = express();
const mongoose = require('mongoose');
const dotenv = require('dotenv');
const cors = require('cors')
const bodyParser = require('body-parser');
require('./passportConfig');
const passport = require('passport');
dotenv.config();
//connect to mongodb
mongoose.set('useFindAndModify', false); mongoose.set('useUnifiedTopology', true);
mongoose.connect(process.env.DB_CONNECT,{ useNewUrlParser:true} , () =>
console.log('connected to db!')
);
//import routes
const authRoute = require('./routes/auth');
//middleware
app.use(bodyParser.json());
app.use(cors());
app.use(passport.initialize());
//error handler
app.use((err, req, res, next) =>{
if(err.name =='ValidationError')
{
var valErrs = [];
Object.keys(err.errors).forEach(key => valErrs.push(err.errors[key].message));
res.status(422).send(valErrs);
next();
}
});
//route middleware
app.use('/api',authRoute);
app.listen(3000, () => console.log("server Up and Running"));```
Any Help please on this one please thank you all
The only thing which is remain is to attach the router which you have define in the auth.js file to your express app like this
const authRouter = require('./auth');
And to prefix all routes define in the auth.js file you attach it as a middleware which is trigger on route prifix with \api
const express = require('express');
const app = express();
// define all your middleware and all other route
// and here you attach the auth router
app.use('\api', authRouter);
This will make authentication available on url http://localhost:3000/api/authenticate
You may also get 404 because of this line in authenticate route (by the way I think this must be a 400 - bad request, not 404, which is making confusion.)
else return res.status(404).json(info);
So to understand this, can you replace your authenticate route like this, and see what logs in the api console:
module.exports.authenticate = (req, res, next ) => {
console.log("req.body: ", req.body)
//calll for passport authentication
passport.authenticate('local', (err, user, info) => {
//error form paasport middleware
if(err) return res.status(400).json(err);
//registered user
else if (user) return res.status(200).json({ "token":user.generateJwt() });
//unknown user or wrong password
else {
console.log("info: ", info)
return res.status(400).json(info);
}
})(req, res);
Also it the angular component, can you change your onSubmit like this for easy debug:
be sure your form.value is correct:
onSubmit(form :NgForm)
{
console.log("form.value: ", form.value);
this.userService.login(form.value).subscribe(
res =>{
this.userService.setToken(res['token']);
this.router.navigateByUrl('/signup');
},
err =>{
console.log("err: ", err.message)
this.serverErrorMessages = err.message;
});
}

Categories

Resources