My TypeScript Software Design & Architecture book just prelaunched! Check out solidbook.io.
Close

Refactoring a Blog Commenting System | Applying SOLID Principles to TypeScript

This article is part of Solid Book - The Software Design & Architecture Handbook. Get the book.
Aug 28th, 2019 / 4 min read / Share / Edit on GitHub
In this article, we take some hastily written Node.js code for a blog commenting system and improve it by applying the SOLID principles.

This article is part of Solid Book - The Software Design & Architecture Handbook w/ TypeScript + Node.js. Check it out if you like this post.

Any code I don't care about the quality of, I call throwaway code. Throwaway code manifests when:

  • We're exploring new technology and testing something out.
  • We're more focused on speed of development and deployment > quality.
  • The code isn't expected to live a long lifetime.

In these scenarios, I might let my inner barbarian out 🦍.

But the moment we write code intended to solve a real problem or address a real need for a business, we should pay close attention to quality.

In fact, it's a good idea to practice writing quality code for even the throwaway stuff. You never know when something that you thought was throwaway code will turn into production code.


Introduction

Recently, I deployed my own comment system on this blog using Express.js, TypeScript, Heroku, and MySQL based on Tania Rascia's guide.

Try it out. Leave a comment at the bottom of this post. Be nice, please?

The Use Cases that I wanted the general Public to be able to fulfill were to:

  • Post a comment => /api/comments
  • GET all the comments for a blog post => /api/comments?url=<slug>

So I quickly threw together an Express app and hooked up the API calls.

The Express server

Here's the main app.ts file. It has two functions getComments and postComment

app.ts
import express from 'express'
import bodyParser from 'body-parser'
import cors from 'cors'
import { commentRepo } from './repos';
import { stripTrailingSlash } from './utils';

const app = express()

app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: true }))

const getComments = async (req, res) => {
  const url = stripTrailingSlash(req.query.url);
  
  if (!!url === false || url.length < 2) {
    return res.status(500).json({ message: "not a valid request" })
  }
  
  try {
    const comments = await commentRepo.getComments(url);
    return res.status(201).json({ comments: comments ? comments : [] });
  } catch (err) {
    console.error(err);
    return res.status(500).json({ message: 'Error', error: err.toString() })
  }
}

const postComment = async (req, res) => {
  try {
    const { name, id, url, comment } = req.body;

    if (name.length < 2 || name.length >= 100) {
      return res.status(400).json({ message: 'Name needs to be within 2 and 100 chars' })
    }

    if (comment.length < 2 || comment.length >= 3000) {
      return res.status(400).json({ message: 'Comment needs to be less than 3000' })
    }

    if (!!id === false) {
      return res.status(400).json({ message: 'Id needs to be provided' })
    }

    if (!!url === false) {
      return res.status(400).json({ message: 'Url needs to be provided' })
    }

    await commentRepo.saveComment({ name, id, url, comment });

    return res.status(200).json({ message: 'Comment posted' });
  } catch (err) {
    return res.status(500).json({ message: 'Error', error: err.toString() })
  }
}

app.get('/comments', getComments);
app.post('/comments', postComment);

app.listen(process.env.PORT || 9021, () => {
  console.log(`Server listening on 9021`)
})
commentsRepo.ts
import { Comment } from '../models/Comment';
import { CommentMap } from '../mappers/CommentMap';

export interface ICommentRepo {
  getComments (url: string): Promise<Comment[]>;
  saveComment (comment: Comment): Promise<any>;
  approveComment (commentId: string): Promise<any>;
  declineComment (commentId: string): Promise<any>;
}

export class CommentRepo implements ICommentRepo {
  private conn: any;

  constructor (conn: any) {
    this.conn = conn;
  }

  getComments (url: string): Promise<Comment[]> {
    const query = `SELECT * FROM comments WHERE url = ?;`;
    return new Promise((resolve, reject) => {
      this.conn.query(query, [url], (err, result) => {
        if (err) return reject(err);
        return resolve(result.map((r) => CommentMap.toDomain(r)));
      })
    })
  }

  saveComment (comment: Comment): Promise<any> {
    const query = `INSERT INTO comments 
    (id, name, comment, created_at, url, approved) 
    VALUES
    (?, ?, ?, ?, ?, ?)`;

    const values = [
      comment.id, 
      comment.name, 
      comment.comment, 
      new Date(),
      comment.url,
      false
    ]

    return new Promise((resolve, reject) => {
      this.conn.query(query, values, (err, result) => {
        if (err) return reject(err);
        return resolve();
      })
    })
  }
}


Discussion

Liked this? Sing it loud and proud 👨‍🎤.


0 Comments

Be the first to leave a comment

Submit

Stay in touch!



About the author

Khalil Stemmler

Khalil Stemmler is a Developer / Designer and co-founder of Univjobs. He frequently publishes articles about Domain-Driven Design and Advanced TypeScript & Node.js best practices for large-scale applications.



View more in Software Design



You may also enjoy...

A few more related articles

Domain Knowledge & Interpretation of the Single Responsibility Principle | SOLID Node.js + TypeScript
Jun 13th, 2019 / 9 min read
The Single Responsibility Principle specifies that a class or function should only have one reason to change. Admittedly, that's n...
SOLID Principles: The Software Developer's Framework to Robust & Maintainable Code [with Examples]
May 18th, 2019 / 15 min read
The SOLID principles are a set of software design principles that teach us how we can structure our functions and classes to be as...
The 6 Most Common Types of Logic in Large Applications [with Examples]
Sep 16th, 2019 / 12 min read
In this article, you'll learn about the Clean Architecture, why we should separate the concerns of large applications into layers,...
Why I Don't Use a DI Container | Node.js w/ TypeScript
Sep 16th, 2019 / 11 min read
Instead of a DI Container, I just package features by component and use logical naming conventions.

Want to be notified when new content comes out?

Join 2000+ other developers learning about Domain-Driven Design and Enterprise Node.js.

I won't spam ya. 🖖 Unsubscribe anytime.

Get updates