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
Related
I'm using TypeScript and Express to create an API that follows the principles of Clean Architecture. I separated each route of my application in a folder, and then I import all of them into an index.ts file, merging each one inside an unique router and exporting the same. My problem is that the importation of this unique file is returning undefined.
I think that is isn't a problem of my code itself, since TypeScript can infer correctly the type of the imported file and doesn't report any error during development. The only error I get just happens when trying to run the code:
TypeError: app.use() requires a middleware function
This is the code snippet that causes the error:
// expressInstance.ts --> Imported by the "server.ts" file, where listening the server happens
import express from "express";
import cors from "cors";
import { router } from ".."; // File with all the routes
const expressInstance = express();
expressInstance.use(express.urlencoded({extended: false}));
expressInstance.use(express.json());
expressInstance.use(cors());
expressInstance.use(router); // Line where the error appears
export {
expressInstance
};
And this is the code of the imported router file:
// index.ts
import { Router } from "express";
import { userRouter } from "./user.routes";
import { postRouter } from "./post.routes";
const router = Router();
router.use("/user", userRouter);
router.use("/post", postRouter);
export {
router
};
I read in another question that the undefined return used to happen at Express version 3, but I use the 4.17.13 version, so I think this isn't the cause of the problem. In fact, I have no idea what could be. I tried to see the router content by using console.log(router) in the index.ts file (before being imported by expressInstance.ts), but it didn't work, because the code wasn't even executed.
Try adding a path to your .use()
e.g:
expressInstance.use('/users', router); // in the case you wanted the router to be for users
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
Hi friends how are you doing?
I'm trying to do something different, I don't know if it's away from the concept itself but it would help me to achieve what I'm trying to do in an elegant way.
I'm using a repository pattern and in the implementation I want to use a overloaded constructor and use an optional argument, basically passing some adicional information when it's needed.
The problem is, it's working great when the constructor is empty, but by the time change de signature to receive one more argument, the TSYSRINGE throws an execption.
I Really think that I'm missing something really simple, but I can't figure what. Could you please help me on this one? Thanks
ERROR:
Error: Cannot inject the dependency at position #0 of "ListProjectsServices" constructor. Reason:
TypeInfo not known for "ProjectsRepository"
Controller
export default class ProjectsController {
public async index(request: Request, response: Response): Promise<void> {
const listProjectsServices = container.resolve(ListProjectsServices);
const projects = await listProjectsServices.execute();
response.json(projects);
}
Service
#injectable()
export default class ListProjectsServices {
constructor(
#inject('ProjectsRepository')
private ProjectsRepository: IProjectsRepository,
) {}
public async execute(): Promise<Projects[]> {
const ProjectsList = await this.ProjectsRepository.findAllProjects();
return ProjectsList;
}
}
Container - to create the injection token
container.registerSingleton<IProjectsRepository>(
'ProjectsRepository',
ProjectsRepository,
);
Repository - Notice the extra_details argument in the constructor
after adding it, the problem occurs
#EntityRepository(Projects)
export default class ProjectsRepository implements IProjectsRepository {
private ormRepository: Repository<Projects>;
constructor(extra_details?: object) {
this.ormRepository = getRepository(Projects);
}
[...]
Today I went through this same problem.
Read this article on circular dependencies.
It tells us to use the delay function imported from tsyringe.
In the article, he tells us to use the delay inside the constructor.
But you, like me, are not directly sending the object in the inject, but a registered key.
Then you must use the delay in your container file, around the repository object try this:
import { container, delay } from 'tsyringe';
container.registerSingleton<IProjectsRepository>(
'ProjectsRepository',
delay(() => ProjectsRepository),
);
Insert the delay in all your injections that depend on an asynchronous repository
It can also be an error in the seo ormconfig.json, in the entities property.
Make sure the path is pointing to your entities:
"entities": [
"./src/modules/**/infra/typeorm/entities/*.ts"
],
We had the same problem here and we didn't want to have to use the delay function in the controller because we would be breaking the dependency injection principle... One way to solve the problem is to import your "Container" file into the main project file, in our case called "server.ts" and installing a lib called "reflect-metadata".
server.ts:
import * as dotenv from 'dotenv';
dotenv.config();
import express from 'express';
import 'express-async-errors';
import 'reflect-metadata'; // here is reflect-metadata import!
import config from './config/application';
import './infra/container'; // here is container import!
import { errorHandler } from './infra/http/middlewares/errorHandler';
import useSwagger from './infra/http/middlewares/swagger';
import { routes } from './infra/http/routes';
import { morganMiddleware } from './infra/logging/morgan';
export const app = express();
app.use(morganMiddleware);
app.use(express.json());
app.use(`${config.prefix}/api`, routes);
app.use(`${config.prefix}`, express.static('public'));
useSwagger(`${config.prefix}/api/docs`, app);
app.all(`${config.prefix}`, (_, res) => res.redirect(`${config.prefix}/api/docs`));
app.use(errorHandler);
container/index.ts:
import { container } from 'tsyringe';
import { RecurrenceNotificationUseCase } from '../../application/useCases/RecurrenceNotificationUseCase';
import { PubSubImplementation } from '../pubsub/PubSubImplementation';
container.registerSingleton('IPubSub', PubSubImplementation);
container.registerSingleton('IRecurrenceNotificationUseCase', RecurrenceNotificationUseCase);
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.
I am working on a small server that will eventually be used as a boilerplate for future servers. Since order can matter for middleware, I reached for compose to pipe them all together in an obvious order. My goal is to add one level of abstraction for the middleware and routes so they can be swapped out later without too many changes to the entry point.
Given this info, does it seem like an anti-pattern to use compose since the middleware/routes themselves are impure and modify the (app) that gets passed in?
'use strict';
import { createServer } from 'http';
import express from 'express';
import { flowRight as compose } from 'lodash';
import { system, environment } from './config';
import routes from './routes';
const setup = compose(routes, system);
const app = setup(express());
createServer(app)
.listen(environment.port, environment.ip, () => {
console.log('Server running!');
});