По моему опыту, обещания начинают казаться намного более естественными, когда вы перестаете пытаться втиснуть их в одну и ту же функцию! (Но все мы, наверное, когда-то писали что-то похожее на ваш пример, не волнуйтесь.)
Меньшие куски кода имеют тенденцию к тому, чтобы их было проще тестировать и отлаживать. Например, если вы знаете, что ваша проверка переменных в теле запроса верна, то, возможно, проблема заключается еще дальше в стеке.
Вот пример использования небольшого стека промежуточного программного обеспечения. Это позволяет разбить операции на куски размером с кусочек, при этом гарантируя, что одно произойдет раньше другого.
const bcrypt = require("bcrypt");
const express = require("express");
const knex = require("knex");
const config = require("./knexfile").development;
const app = express();
app.use(express.json());
const db = knex(config);
const detailValidator = (req, res, next) => {
// You can do more robust validation here, of course
if (!req.body.firstName || !req.body.lastName) {
return next(new Error("Missing user details."));
}
next();
};
const userUniqueValidator = (req, res, next) => {
db("users")
.where("username", req.body.username)
.then(users => {
if (users.length !== 0) {
return next(new Error("User exists."));
}
next();
});
};
const userCreator = (req, res, next) => {
const { username, password, firstName, lastName } = req.body;
const hash = bcrypt.hashSync(password, 10);
db.transaction(trx =>
trx("users")
.insert({
username,
first_name: firstName,
last_name: lastName
})
.then(([userId]) => trx("auth").insert({ user_id: userId, hash }))
.then(() => res.json({ success: true }))
).catch(err => next(err));
};
app.post("/", detailValidator, userUniqueValidator, userCreator);
app.use((err, req, res, next) => res.json({ error: err.message }));
app.listen(4000, () => console.log("yup"));
Относительно транзакций в Knex: на самом деле вам вообще не нужно звонить commit
, если используется вышеуказанный синтаксис. Однако вам необходимо использовать аргумент trx
в качестве построителя запросов. Документация также предлагает другой вариант, который является синтаксисом transacting
: см. docs .
Наконец, я бы не советовал использовать ваше имя пользователя в качестве первичного ключа. Они слишком часто требуются для изменения, и всегда есть риск случайной утечки в URL или журнале. Однако я бы порекомендовал включить уникальное ограничение. Может быть, что-то подобное?
exports.up = knex =>
knex.schema.createTable("users", t => {
t.increments("id");
t.string("username").unique();
t.string("first_name");
t.string("last_name");
});
exports.up = knex =>
knex.schema.createTable("auth", t => {
t.increments("id");
t.integer("user_id").references("users.id");
t.string("hash");
});
Стоит отметить, что я использовал SQLite3 для этого быстрого примера, который поддерживает возвращение идентификатора строки только после вставок (отсюда [ userId ]
в предложении then
после вставки пользователем).