How to create dynamic, protected routes for next.js middleware - javascript

I have created a file, routes.ts that stores routes I would like to protect. This works for regular routes, such as my '/profile' route, but when I try to add a dynamic url, it does not work (a user that is not authenticated can still view these routes).
routes.ts
export const protectedRoutes = ["/profile", "/profile/[id]", "/timeline/[id]", "/"];
export const authRoutes = ["/login"];
export const publicRoutes = [];
middleware.ts
export function middleware(request: NextRequest) {
const currentUser = request.cookies.get("currentUser")?.value;
if (
protectedRoutes.includes(request.nextUrl.pathname) &&
(!currentUser || Date.now() > JSON.parse(currentUser).expiredAt)
) {
request.cookies.delete("currentUser");
const response = NextResponse.redirect(new URL("/login", request.url));
response.cookies.delete("currentUser");
return response;
}
if (authRoutes.includes(request.nextUrl.pathname) && currentUser) {
return NextResponse.redirect(new URL("/profile", request.url));
}
}```
I have logged out of my application and tried to view the dynamic routes. If my code was correct, I should have been rerouted to my login page, however, it still shows the data even though I am not authenticated. To make sure the protected routes work, I viewed my static routes, and was successfully rerouted to the login page.

Try creating a config variable in the middleware and apply the middleware on the routes you want to protect. For example you can add this to the middleware for the dynamic routes:
export const config = {
matcher: ["/profile/:path*", "/timeline/:path*"]
};
Please note if include the config in your middleware, the middleware will ONLY be applied to the routes in the matcher array. I just added the above code for the dynamic routes you had as an example.
The matcher is an array of routes to apply the middleware on. It supports the wildcard syntax so you can match a group of routes for the dynamic routes. For example, the /profile/:path* part will Apply the middleware on all the routes that starts with /profile. It will match the routes like /profile/123. Read more about config and matcher here.

Related

Express.js returning multiple routes in a single exported router object returns incorrect route

I am trying to utilize an MVC pattern for express. I am modularizing routes and trying to declare the express router in only the server entry file and nowhere else.
My current issue is when exporting my appRoute function in my main routes file (see below), the first route (user route), returns users. I have another route called game that is exported in the same function but it still returns users instead of games.
Both routes have a controller function called getAll that gets different data from different tables.
If I try and visit the route: http://localhost:8000/user/getAll, it returns all users just fine.
If I try and visit the route: http://localhost:8000/game/getAll, it still returns all users even when they're different routes...
If I were to flip the order of users and games in the main routes file where game is first and user is second, users starts to return games. It's like the second route mimics the first route.
This may be something simple, but any help I will appreciate.
My code is as shown below
server entry point (index.js)
const app = express();
const router = express.Router();
const bootstrap = require('./src/bootstrap');
bootstrap(app, router);
I am passing on the app and router instance to my bootstrap file where all routes will be exported to.
bootstrap.js (this file gets all exported routes and uses them within the app)
const { appRoute } = require('./routes');
module.exports = (app, router) => {
return app.use('/', appRoute(router));
};
I am passing on the router instance to my main routes file where all routes are exported from.
Main routes file (index.js): this file requires all routes and uses them within the router instance. I think this might be where my issue is but I am a little stuck on how I might fix it.
const { userRoute } = require('./userRoute');
const { gameRoute } = require('./gameRoute');
exports.appRoute = (router) => {
router.use('/user', userRoute(router));
router.use('/game', gameRoute(router));
return router;
};
Game route files (index.js): returns all users instead of games
const { gameController } = require('../../controllers');
exports.gameRoute = (router) => {
return router.get('/getAll', gameController.getAll);
};
Any help is greatly appreciated. If there is any clarification needed please let me know.
I think you need to create a separate router for game and user.
See the express.Routing section and birds.js example here

Nodejs how can I add another main app.js and use both of them to make my code clean?

Hello I'm using nodejs and express framework and I wrote all my serverside code into my app.js file but it's little bit complicated for me cause cause I have almost 250 line code and I want to implement authentication now so I want to create another app.js to write my code only for auth so other code will not confuse me how can I do that????
UPDATED
as you se above I have 2 post requests from my app.js I asking that how I will get thoso request in my auth.js file cause as I understand exports import for static js field I want to take request and save that information my database and I want to that in my auth.js file
Here are a simplified structure for your project derived from my project at this link.
routes.js:
Create a file named route.js where you define all the routes for your application. In this case the routes will be only the register and the login routes that will be handled by the UserController module.
import { Router } from 'express';
import UserController from './UserController';
const router = Router();
router.post(
'/register',
UserController.register
);
router.post(
'/login',
UserController.login
);
export default router;
UserController.js:
This file/class that handle all the operations for the creation and login of an user
As you can see all the methods has no route url because they are called directly from our route.js file. We are dividing and structuring your application!
export default class UserController {
public static register(req, res) {
// Register operation
}
public static login(req, res) {
// Login operation
}
}
app.js
The entry point and where you are configuring your express application.
import express from 'express';
import routes from './routes';
const app = express();
// configure app ...
// Here we attach our routes url to the express app
app.use('/', routes)
Hope it helps :)
depending on your setup you can divide your files in per example:
app.js
authentication.js
and then either require or import functions from the authentication.js file into app.js like this:
import express from "express";
or this:
const express = require("express");
functions inside the authentication file should be exported like this:
//needs to be imported as this: import {authenticate} from "authenticate";
export function authenticate(){};
//needs to be imported as this: import authenticate from "authenticate";
function authenticate(){};
export default authenticate;
or this:
// needs to be imported as this: const authenticate = require("authenticate");
module.exports = function authenticate(){};
See this guide on enabling ES6 imports as shown above. this has the preference for it can save on memory when importing.

Is it possible to use application level middleware in express router?

I've been playing around with setting up a basic atlassian-connect-express (ACE) application, and have modified the starter code provided by the ACE package to be suitable for serverless deployment. One of the problems I faced after doing this was that routing is now divided into stages, e.g. /dev, /prod. I did a bit of research and found that a way to deal with this would be to use an express Router and mount it to the appropriate endpoint for the stage being deployed to. The problem I then faced is that the authentication middleware provided by ACE seems to be application level and then can't be used by each router.
Typically the routes were added to the express app like this:
import ace from 'atlassian-connect-express';
import express from 'express';
import routes from './routes';
const app = express();
const addon = ace(app);
app.use(addon.middleware());
routes(app, addon);
and in ./routes/index.js
export default function routes(app, addon) {
// Redirect root path to /atlassian-connect.json,
// which will be served by atlassian-connect-express.
app.get('/', (req, res) => {
res.redirect('/atlassian-connect.json');
});
// This is an example route used by "generalPages" module (see atlassian-connect.json).
// Verify that the incoming request is authenticated with Atlassian Connect.
app.get('/hello-world', addon.authenticate(), (req, res) => {
// Rendering a template is easy; the render method takes two params:
// name of template and a json object to pass the context in.
res.render('hello-world', {
title: 'Atlassian Connect'
});
});
// Add additional route handlers here...
}
I've changed ./routes/index.js to work as a router object and export that, however this leaves me unable to use the addon.authenticate() middleware
import ace from 'atlassian-connect-express';
import express from 'express';
import routes from './routes';
const app = express();
const addon = ace(app);
app.use('/dev', require('./routes'));
and in ./routes/index.js
const express = require('express');
const router = express.Router();
// Redirect root path to /atlassian-connect.json,
// which will be served by atlassian-connect-express.
router.get('/', (req, res) => {
res.redirect('/atlassian-connect.json');
});
// This is an example route used by "generalPages" module (see atlassian-connect.json).
// Verify that the incoming request is authenticated with Atlassian Connect.
router.get('/hello-world', addon.authenticate(), (req, res) => {
// Rendering a template is easy; the render method takes two params:
// name of template and a json object to pass the context in.
res.render('hello-world', {
title: 'Atlassian Connect'
});
});
module.exports = router;
Obviously having no knowledge of addon, the router cannot use that authentication middleware.
Is it possible to pass that middleware through to the router when attaching it to the application? If not, is there another way I can handle URL prefixes without using a router?

Difference between app.get and router.get (Express)

I personally prefer the first approach as I feel it keeps my logic inside the controller and my routes are super obvious when all they do is use a method from a controller and map it to a route.
But I am a very junior developer and I keep finding that people use the second approach more. Would anyone with more knowledge than I can explain to me if my approach is bad and why. I understand both, but as I said I am barely beginning my developer career and I need to make my code readable/simple so I need to learn what approach is better for what case.
Thanks!
//this is /routes/product.ts
import product from "../controllers/Product";
module.exports = function(app: Application) {
app.get("/api/product", product.getAll);
app.post("/api/product", product.create);
app.get("/api/product/:_id", product.getOne);
app.put("/api/product/:_id/edit", product.update);
app.delete("/api/product/:_id", product.delete);
};
---------------------------------------------------
//this is app.ts
import express, { Application } from "express";
class App {
app: Application;
constructor() {
this.app = express();
this.routes();
}
routes() {
require("../routes/product")(this.app);
}
}
}
export default App;
//this is .routes/index.ts
import { Router, Request, Response } from "express";
import Product from "../models/Product";
const router = Router();
router
.route("/create")
.get((req: Request, res: Response) => {
res.render("product/create");
})
.post(async (req: Request, res: Response) => {
const { title, description } = req.body;
const newProduct = new Product({ title, description });
await newProduct.save();
res.redirect("/product/list");
});
export default router;
----------------------------------------------------
//this is app.ts
import express, { Application } from "express";
import indexRoute from "./routes/index";
class App {
app: Application;
constructor() {
this.app = express();
this.routes();
}
routes() {
this.app.use(indexRoute);
}
}
export default App;
There is absolutely no difference, in fact, app is actually is a Router internally (or at least it uses one internally).
The main advantages of using a Router would be:
Router level middleware, your approach would mean any middleware added would be applied for all routes.
Router level error handling, you can catch an error and stop it from bubbling up to the global error handler (if necessary).
Relative URLs, your approach means you need to specify the full sub-path each time i.e. /api/products/:_id/edit Vs /:_id/edit
Routers plugin really nicely to Express (they work just like any other middleware)
Easier to unit test if necessary

React and Dynamic Routes including Filename and Extension

I am new to React and trying to put a CMS driven application together as kind of a POC. I am majorly stuck on how to get a full URL (including filename and ext) into a dynamic route in a React application. For example if I call this
http://localhost:8080/en/us/fly/index
The dynamic route picks it up. But if I call this
http://localhost:8080/en/us/fly/index.html
I get the following error. Cannot GET /en/us/fly/index.html
It is a requirement that I handle all URLs through my route even if they have a file extension & filename. My routes.jsx is as follows:
import React from 'react';
import {
Switch, Route
} from 'react-router-dom';
// Pages
import Page from "./pages/Page";
class Routes extends React.Component {
render() {
return (
<Switch>
<Route path="/:page" component={Page} />
</Switch>
)
}
}
export default Routes;
and my server.js is as follows:
const path = require('path')
const express = require('express');
const app = express();
const port = process.env.PORT || 8080;
// get an instance of router
const router = express.Router();
// serve static assets normally
router.use(express.static(__dirname + '/dist'))
router.get('*', function (request, response){
response.sendFile(path.resolve(__dirname, 'dist', 'index.html'))
})
// apply the routes to our application
app.use('/', router);
app.listen(port);
console.log('Site available on port ' + port);
How can I get all URLs to pass through my dynamic route? Is this even possible? I need to know if there are limitations in React that prevent this vs how it would work in a traditional ASP.Net MVC application where I can do this kind of dynamic routing.
Any help or advice on this will be very much appreciated as I am very stuck with this and don't fully understand the React world enough to find the solution.
Thanks in advance,
You can use *.* in your route. It matches with file extensions as below
<Route path="*.*" component={YOUR_COMPONENT}/> // matches /hello.jpg and /hello.html
Then you can access the props in your component as
// CMS name: sample.html
this.props.params.splat[0] // For name of prop without extension. It Will give 'sample'
this.props.params.splat[1] // For extension itself. It will give 'html'

Categories

Resources