API REST básica para manipular uma coleção MongoDB, usando Node, Express e Mongoose

Sep 09 2020

Estou praticando programação back-end e NodeJS. Como exercício, estou tentando construir uma API REST para uma coleção MongoDB. Também estou aprendendo a usar os middleware Express e Mongoose, então é isso que usaremos para o servidor e o banco de dados, respectivamente. Também praticando async / await para lidar com promessas.

Os requisitos para esta API REST básica e exercício são:

  • Suporte para obter e excluir recursos individuais.
  • Suporte, obtenha e poste na coleção de recursos.
  • Aplicar generalização e separação de interesses.
  • Proteger contra injeção de Mongo.
  • Use async / await para lidar com promessas.

Esta é a implementação de trabalho atual:


app.js

const express = require('express')
const mongoose = require('mongoose')
const morgan = require('morgan')

const songRouter = require('./routes/song-router.js')

const mongurl = 'mongodb://localhost:27017/library'
const port = 3000

const app = express()
app.use(morgan('combined'))
app.use('/songs', songRouter)

mongoose.connect(mongurl, () => {
  console.log(`\n    >> Mongoose connected to ${mongurl}`) }) app.listen(port, () => { console.log(`\n >> Node listening to port ${port}`)
})

models / song-model.js

const mongoose = require('mongoose')

const song = {
  name: {
    type: String,
    required: true
  },

  author: {
    type: String,
    required: true
  },

  key: String
}

const options = {
  timestamps: true
}

const schema = new mongoose.Schema(song, options)

module.exports = mongoose.model('song', schema)


routes / song-router.js

const express = require('express')

const control = require('../controllers/song-control.js')

const router = express.Router()
router.use(express.json())

router
  .route('/')
  .get(control.getAll)
  .post(control.postOne)

router
  .route('/:songId')
  .get(control.getOne)
  .delete(control.deleteOne)

module.exports = router


controladores / song-control.js (versão 1, sem generalização)

const songModel = require('../models/song-model.js')

exports.getAll = async (req, res, nxt) => {
  try {
    const allSongs = await songModel.find({})
    res.status(200).json(allSongs)
  } catch (err) {
    nxt(err)
  }
}

exports.getOne = async (req, res, nxt) => {
  try {
    const oneSong = await songModel.findById(req.params.songId)
    res.status(200).json(oneSong)
  } catch (err) {
    nxt(err)
  }
}

exports.postOne = async (req, res, nxt) => {
  try {
    const postedSong = await songModel.create(req.body)
    res.status(200).json(postedSong)
  } catch (err) {
    nxt(err)
  }
}

exports.deleteOne = async (req, res, nxt) => {
  try {
    const deletedSong = await songModel.findByIdAndDelete(req.params.songId)
    res.status(200).json(deletedSong)
  } catch (err) {
    nxt(err)
  }
}


controladores / song-control.js (versão 2, primeira tentativa de generalização)

const songModel = require('../models/song-model.js')

exports.getAll = buildMongoFunction('find')

exports.getOne = buildMongoFunction('findById', true)

exports.postOne = buildMongoFunction('create', false)

exports.deleteOne = buildMongoFunction('findByIdAndDelete', true)

function buildMongoFunction (funName, argIsParam) {
  return async (req, res, nxt) => {
    const arg = argIsParam ? req.params.songId : req.body

    try {
      const reply = await songModel[funName](arg)
      res.status(200).json(reply)
    } catch (err) {
      nxt(err)
    }
  }
}



Estou ansioso por todos os tipos e tipos de feedback: estilo, bugs, antipadrões, maneiras de fazer isso de maneira mais concisa / sustentável / redefinível, convenções, práticas recomendadas; tudo o que você acha que pode ser melhorado, por favor, compartilhe.

Tenho algumas perguntas específicas, mas fique à vontade para ignorá-las e comentar sobre outra coisa!

  • A generalização de controllers / song-control.js parece hacky. Existe uma maneira melhor de implementar a generalização desse padrão? Como você fez isso?

  • Quão bem esses conceitos estão sendo aplicados: generalização, separação de interesses? Você separaria responsabilidades ainda mais? Ou eles estão muito separados? Algo pode ser mais generalizado?

  • O assíncrono / espera está sendo usado?

  • Devo higienizar os insumos? Ou a imposição de modelos e esquemas de proteção Mongoose é suficiente contra injeções de Mongoose?

  • Parece que as consultas do Mongoose não retornam promessas . O código assíncrono / espera aqui está fazendo algum trabalho assíncrono real?

  • O que você recomendaria fazer de uma maneira diferente?

Respostas

2 santanaG Sep 13 2020 at 08:12

Esta não é uma resposta completa à sua pergunta, mas o que eu poderia anotar rapidamente enquanto olhava para sua postagem. Desculpe, não há insights para compartilhar. Eu o desencorajaria a escolher essa resposta, caso alguém com uma resposta mais detalhada decida vir e fornecer mais valor.

Eu olhei para o controlador de música. Considerei as opções de estilo e organização e decidi o seguinte. Isso é mais sobre o estilo do javascript em geral, não necessariamente sobre o nó, ou mangusto, ou tecnologia do lado do servidor.

Por favor, dê uma olhada e veja se isso o ajuda a pensar de forma diferente sobre qualquer coisa que você deseja entender.

const buildMongoFunction = name => async (req, res, nxt) => {
  const { params: { songId }, body } = req;
  
  try {
    const needsSongID = ['findById', 'findByIdAndDelete'].includes(name);
    res.status(200).json(await songModel[name](needsSongID ? songID : body));
  } catch (error) { nxt(error); }
};

const toExports = ['find', 'findById', 'create', 'findByIdAndDelete']
  .reduce((toExport, name) => ({ ...toExport, [name]: buildMongoFunction(name) }), {});

Object.assign(exports, toExports);