Есть много способов разделить код, который вы описали.Я возьму это по частям.
Сначала извлеките все настраиваемые переменные и настройте один файл, который может получить их из среды (возможно, с настройками dev по умолчанию, на ваш выбор).Вы можете использовать библиотеку, такую как commander или convict , но, честно говоря, я предпочитаю просто написать простой файл, который извлекает их сам:
// ./config.js
module.exports = {
pool: {
user: process.env.DB_USER || 'admin',
password: process.env.DB_PW || 'test123!',
host: process.env.DB_HOST || '127.0.0.1',
port: process.env.DB_PORT || '5432',
database: process.env.DB_NAME || 'test_db'
}
};
Что касается вашей базы данныхзвонки, некоторые люди любят использовать ORM-подобные вещи, такие как sequelize , но опять же я склонен начинать с простых и добавлять вещи по мере необходимости.В вашем случае вы должны подумать о том, какие стандартные элементы вы можете создать из общего кода, а затем обернуть их в простые модули, которые открывают доступ только для того, что действительно нужно для вызывающего кода.Например, вы заметите, что большинство ваших маршрутов будут соединяться с пулом, проверять наличие ошибок, затем запускать запрос, если он не выдает ошибку, и, наконец, отображать либо ошибку, либо результаты запроса, верно?Так что все это можно обернуть в довольно простую функцию запроса, которая обрабатывает шаблон внутри системы и работает только с выражением запроса и обратным вызовом, например:
// ./db/index.js
const pg = require('pg');
const config = require('./config');
const pool = new pg.Pool(config.pool);
function query(sql, params, callback) {
// maybe check for valid inputs here or something, but at least normalize in case folks don't pass params
if(arguments.length < 3) {
callback = params;
params = null;
}
pool.connect((err, client, done) => {
// just exit here and let the calling code know there was a problem
if(err) return callback(err);
// I haven't tested this w/ the pg library recently, you might have to do two of these if it doesn't like null as a second argument
client.query(sql, params, (err, result) => {
if(err) return callback(err);
done();
// calling code probably doesn't care about anything but rows, but you can do other stuff here if you prefer
return callback(null, result.rows);
});
});
};
// You can also add additional functions if you want shorthand for doing things like query by ID or with params, or similar
module.exports = { query };
Я также думаю, что это может быть полезно для хранениястроки SQL где-то централизованно, или на объектах модели, просто для того, чтобы примечание кода маршрутизации об этом позаботилось.Для очень простого примера, использующего ваши два маршрута, я мог бы сделать что-то вроде этого:
// ./db/queries.js
module.exports = {
RECIPES: {
LIST: 'SELECT * FROM recipes;',
FIND_BY_ID: 'SELECT * FROM recipes WHERE recipes_id = $1;'
}
};
Хорошо, теперь ваш код маршрутизации может быть довольно простым, вы можете просто получить модуль db и выполнить запрос,позволить маршрутизации беспокоиться только о том, что она должна делать с запросом и ответом.Другим вариантом, который нравится людям, является фактическое создание модуля для каждой модели в вашем приложении (например, Рецепт), который оборачивает два вышеупомянутых файла в набор статических функций, так что ваши маршруты даже не знают, что они запрашивают конкретно.В этом случае вызовы будут выглядеть как Recipe.list(cb)
или Recipe.findById(id, cb)
.Этот стиль стал популярным в Ruby on Rails несколько лет назад, он получил неоднозначное признание в сообществе Node, но я упоминаю его для полноты.
// ./routes/recipes.js
const router = require('express').Router();
const db = require('./db');
const queries = require('./db/queries');
router.get('/api/recipes', (req, res, next) => {
db.query(queries.RECIPES.LIST, (err, rows) => {
if(err) return next(err);
return res.send(rows); // status 200 is the default here
});
});
router.get('/api/recipes/:id', (req, res, next) => {
const id = req.params.id;
db.query(queries.RECIPES.FIND_BY_ID, [id], (err, rows) => {
if (err) return next(err);
return res.send(rows);
});
});
Наконец, в вашем основном установочном файле Express:
// ./app.js
const express = require('express');
const app = express();
const recipeRoutes = require('./routes/recipes') // note if you have an index.js file that gets imported just by calling for the folder, so that's a way to group features as well
app.use(recipeRoutes);
// I'm a big fan of error handling middleware. There's a more complex approach I did in [praeter][4] that gives you http-verb based errors that you can then catch and send the appropriate status, but that's again more complex than you might need here.
app.use((err, req, res, next) => {
// this can be as simple or as complex as you like.
// note it's a best practice to send only "clean" messages to the client, so you don't give away that you're using a Postgres db or other stuff that makes hacking easier.
console.error(err);
res.status(500).send('Oops! Something went wrong!!');
});
Очевидно, что есть много способов снять шкуру с этой кошки, поэтому я бы порекомендовал в основном просто искать, где выповторяешь, а потом рефакторину повторять меньше.Кроме того, если вы заинтересованы в создании более готовых к работе приложений в целом, то приложение 12 factor необходимо прочитать.