API REST básica para manipular uma coleção MongoDB, usando Node, Express e Mongoose
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
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);