Improve Legacy Node.js App Structure

by mmyoji

2 min read

A main project I currently tackle on adopts DDD architecture. But nobody in the project understands it correctly:

  • business logic spreads across everywhere
  • empty Entities just like DTO
  • Service classes with no business logic
  • etc.

Due to this situation, I'm now trying to improve application structure. Of cource, it's the best to use a framework that has strict rules about app/dir structure. The project is a legacy, express.js based one, tho.

One important thing is reducing roles (layers). There're many layers and objects in the current architecture. Everyone confuses what/where things should be put. And I realized it makes sense that Ruby on Rails adopts MVC patterns.

Structure

One of the ideas will be based on NestJS approach (see the tutorial).

Web applications are normally the following 4 steps:

  1. receives a request
  2. validates it
  3. applies business logic
  4. returns a response.

To acheive this, app structure can be like this:

app/
  posts/
    posts.router.ts
    posts.handlers.ts
    posts.validator.ts
    posts.service.ts
  • posts.router.ts handles /posts/* requests.
  • posts.service.ts hanldes a part of 3.
// posts.router.ts
export const postsRouter = defineRouter("/posts", (r) => {
  // GET /posts
  r.get("/", indexHandler);

  // GET /posts/:id
  r.get("/:id", showHandler);

  // GET /posts/new
  r.get("/new", newHandler);

  // POST /posts
  r.post("/", createHandler);

  // GET /posts/:id/edit
  r.get("/:id/edit", editHandler);

  // PATCH /posts/:id
  r.patch("/:id", updateHandler);

  // DELETE /posts/:id
  r.delete("/:id", destroyHandler);
});

// posts.handlers.ts
export const indexHandler = defineHandler(async (req, res) => {
  const options = validateSearchOptions(req.query);
  const { posts, total, cursor } = await service.fetchList(options);
  res.json({ data: { posts }, total, cursor });
});

export const createHandler = defineHandler(async (req, res) => {
  const data = validatePost(req.body);
  const post = await service.create(data);
  res.status(201).json({ data: { post } });
});

You can also arrange the structure like this:

app/
  posts/
    posts.router.ts
    posts.service.ts
    _index/
      index.handler.ts
      index.validator.ts
    _create/
      create.handler.ts
      create.validator.ts

it can be like the following for more realistic monolith apps:

apps/
  admin/   # admin.example.com/
    posts/
      posts.router.ts
      # ...
  web/     # www.example.com/
    posts/
      posts.router.ts
      # ...

Other common layers like persistent layer, middlewares, logger, etc. is like this:

app/
  # ...
lib/
  entities/
    post.ts
    user.ts
  middlewares/
    not-found.middleware.ts
    server-error.middleware.ts
  repositories/
    post.repository.ts
    user.repository.ts
  logger.ts
  define-handler.ts
  define-router.ts

ActiveRecord pattern vs Repository pattern

I'd thought Repository pattern would be better than ActiveRecord pattern for several years. But I have to consider using AR if project members aren't skilled enough.

Final Comment

I'm just trying this now and don't know what I feel in future.