чорнетка
Last updated
Last updated
У налаштуванні Mongo Atlas ми отримали URL підключення до бази даних. Пропишемо його у нашому файлі .env в проєкті. І при завантаженні середовища посилання на підключення до бази даних буде зберігатися в process.env.MONGO_URL.
Для роботи з базою даних MongoDB в проєкті будемо використовувати додатковий пакет .
Проведемо далі факторинг .
Створимо папку models, а в ній файл userModel.js. По суті це ніби форма в якій ми описуємо структуру даних, які будемо зберігати.
З бібліотеки mongoose отримуємо Schema. На основі цього класу створюємо нову схему в яку передаємо обʼєкт з описом всіх параметрів.
Ми знаємо, що змінна буде рядок, або число тощо, то можемо просто прописати name: String або year: Number.
Якщо хочемо докладніше описати структуру даних, то описуємо кожен параметр в обʼєкті. Тут type - формат даних, required - що параметр обовʼязковий. unique - вказує що дані в такому полі мають бути унікальні. В даному полі (як на прикладі коду) можна вказати масив, де перший параметр true, а другий - наше кастомне повідомлення у разі помилки.
Оскільки створюємо базу користувачів, то можна розмежувати їх за правами доступу. для цього визначають ролі користувачів. У схемі в параметрі enum вказують масив значень, яких може набувати цей параметр. default відповідно вказує, яке буде значення за замовчанням. У прикладі ці значення винесені в окремий файл const. Також напроти поля password вказано select: false - це означає, що за GET-запитом із результатів буде прибиратися поле пароля, що ми побачимо далі.
const User = model('User', userSchema); - по суті створення моделі, назву якій задаємо як перший параметр (з великої літери), а другий вказується схема моделі.
Сталі значення зазвичай заведено виносити в окремі файли. (це, наприклад переліки можливих варіантів вибору). Якщо подивимося, то ми їх використовуємо у схемі налаштувань користувача вище. Винесли в окремий файл, бо можемо використовувати й в інших модулях нашого проєкту.
Тепер трохи рефакторнемо нашу валідацію Joi. Зокрема під нашу схему напишемо валідацію перевірки. Для пароля будемо використовувати патерн із регулярного виразу. У ньому {8,128} означає, що пароль має бути від 8 до 128 символів. Також обовʼязково повинна бути велика, мала літера, число і якийсь спецсимвол.
Розберемо змінену валідацію прописану раніше.
Із нового тут regex - приймає значення і перевіряє на відповідність регулярному виразу вказаному в круглих дужках.
Там де перевірка ролі користувача у полі valid вказуються всі можливі значення.
Дотепер валідацію ми проводили в контролерах, але логічно винести валідацію у проміжне пз (middleware). Спочатку підключаємо нашу функцію валідатора Joi userValidators. Функція буде асинхронна тому ми її огортаємо в catchAsync.
Далі викликаємо функцію валідації Joi. Передаємо в неї всі дані які отримали (req.body). Як відповідь перевірки отримуємо error і value. Якщо є помилка, то валідація не пройдена, генеруємо помилку.
Далі в цьому ж проміжному пз перевіримо чи є такий email в базі. Для цього уже будемо використовувати створену раніше модель, тому її спочатку підключаємо. і за допомогою методу exists (бібліотеки mongoose) перевіряємо чи є вже такий email користувача в базі даних. Якщо є, то генеруємо помилку. Якщо немає, то перевірка пройдена, перезаписуємо провалідовані дані та передаємо виконання коду наступній функції обробки.
Після цього функцію валідації checkCreateUserData потрібно вказати в роутах, як проміжне пз.
Щоб мати доступ до бази даних потрібно її під'єднати у головному файлі index.js.
Для цього імпортуємо mongoose і підключаємо перед middleware нашу базу даних. Далі наведено один із прикладів підʼєднання бази даних через метод connect. І оскільки це асинхронна операція, то повертає проміс, тому прописуємо then і catch. Якщо помилка виникає, то виходимо з процесу.
Тепер нам тут не потрібно генерувати id та записувати в файл, тому коментуймо, або видаляємо наступні бібліотеки.
Модель підключаємо в контролерах. Створюють користувача за допомогою метода create. А шукають користувачів за допомогою метода find.
Можна перевірити всі хибні варіанти переданих даних і подивитися які видаються помилки в консолі, а також, що повертає сервер. І врешті зробити POST-запит на сервер з коректними даними.
Після цього перевірити дані, які надійшли в базу даних:
Як бачимо, у відповідь із сервера приходить створений запис із вмістом password, чого не має бути.
Щоб на GET-запит не повертався пароль ми прописали у схемі.
Також для GET-запитів у контролерах можна вказати параметри, які ми хочемо повернути (в прикладі повернеться тільки імʼя і рік):
Або параметри, які хочемо прибрати із результату, який повертається (в прикладі повернеться все крім імені (ну і пароля, який не повертається через налаштування схеми)):
Якщо ми хочемо примусово витягнути якесь поле, яке в схемі позначено, що не провертається (пароль) то потрібно вказати +.
Також може повертатися версія обʼєкта, яку можна прибрати і не повертати
Але при створенні нового юзера у нас все одно у відповідь повертається обʼєкт з паролем. тобто цей select не відпрацьовується на новостворений обʼєкт. Для цього при створенні користувача перед поверненням присвоїмо паролю значення undefind. Тобто приховані дані з res потрібно завжди ЗАБИРАТИ!!
У моделі можна іще додати опціонально обʼєкт з конфігами
versionKey: false - означає, що нам не буде повертатися ключ версії __v і не треба вказувати, зоб його прибирати, як було розглянуто раніше
timestamps: true - додає в базу даних інформацію, коли обʼєкт створено і коли обʼєкт оновлено. (буває корисно)
спочатку перепишемо middleware перевірки id користувача(пам'ятаємо, що в рутах він іде як userId).
За допомогою Types ми можемо перевірити чи валідний сам по собі userId об'єкту _id. Друга перевірка - чи існує такий користувач із таким полем.
В роутах ця middleware уже прописана раніше, бо ми її тільки рефакторили. Тому там нічого правити не треба.
В контролерах пропишемо обробку вибірку користувача за id.
Валідація оновлення даних. спрочатку пропишемо валідатор Joi в нащих утилітах. На перевірку вдповідності формату даних. PASSWD_REGEX - ми вказували раніще у файлі, коли прописували валідацію формату даних під час створення користувача. Також прибрали необхідність приходу всіх полів, оскільки з фронтенда може прийти оновлення тільки деякі параметри.
Пропишемо middleware перед оновленням користувача. спочатку перевіряємо на відповідність формату даних, які приходять. а потім перевіряємо чи існує користувач із таким email в базі даних, якщо захочемо оновити пароль. Якщо аткий пароль існуєу іншого користувача то повертаємо помилку. якщо ваділація пройдена і немає збігів, то керування передається наступній функції в роутах. Тут тікавий синтаксис { email: value.email, _id: { $ne: req.params.id } }. Тут ми перевіряємо чи є такий email в базі даних, але за умовим що цей імейл не в поточному записі. тобто. такий імейл має бути тільки в одному записі в тому, який саме і оновлюємо. $ne - означає not exist
Вкажемо цю перевірку в роутах
рефакторинг контроллера:
Як найпростіший варіант:
АЛЕ ПРАВИЛЬНО ОНОВЛЮВАТИ ДАНІ В БД ПОЕТАПНО ОСТ ТАК ЧЕРЕЗ МЕТОЛ SAVE (але тут ми двічі звертаємося до бази даних):
Для захисту даних потрібно хешувати паролі. І зберігати їх у базі даних лише у такому форматі. Для хешування паролів існує багато модулів.
Захешуємо пароль на етапі створення юзера в контроллерах. Спочатку створюється "соль" в яку передаємо цифрою рівень захищенності. зазвичай в проміжку мід 8 і 12. А щоб захешувати пароль потрібно використати метод hash і в нього передати пароль який хочемо захешувати і друшим параметром "соль". Новий користувач - че розпилений користувач із перезаписаним паролем тобто хешем замість нього.
Тепер в базу даних буде записуватися не пароль а його хеш. У такому разі виникає потреба звірення правильності ведення пароля. Для цього у bcrypt існує метод compare. У нього 1-м передають параметр для порівняння А 2-м наш захешований пароль. Він повертає true або false.
Можна провести рефакторинг попереднього коду. Зазвичай хешування як додатковий сервіс виносять окрему функцію в папку utils.
Але є й інші варіанти - з використанням хуків. Свого роду гачок, який спрацьовує, коли відбувається певна подія.
Розглянемо для цього ще одну опцію створення запису в базі даних на прикладі контролера створення користувача
Але повернемо на створення користувача такий синтаксис, на нього теж реагуватиме подальгий код. Тобто подальштй хук буде відпрацьовувати на методи create і save.
І є мождивість проведення операцій в схемі перед самим створенням запису. Реагуватиме на метод save. Для цього порефакторимо нашу модель. підключаємо bcrypt, а потім використовуємо userSchema.pre. Пергий параметр - на який буде реагувати цей алгоритм, друший параметр колбек функція. ми перевіряємо, якщо не було змін в паролі, то виходимо, а якшо були, то хешуємо пароль і перезаписуємо його. ТОбто цей хук буде хешувати наш пароль при створенні чи збереженні юзера.
????
custom mongoose metod
можна створити у схемі метод наприклад checkPassword, і це асинхронна функція яка повертає проміс. і порівнює вона кандидата і хеш
порядок написання бекенд додатку
запустити сервер (express + пережбачити помилку сервера і коли немає збігу 404 )
в пекедж json налаштувати nodemon і NODE_ENV В залежності в якому режимі запустили сервер
налаштувати оточення env. і завантаження відповідного env в задежності від NODE_ENV
прописати в оточенні урл на могно дб
підключити в головному файлі програми базу монго дб
пілключити всі необхідні мідлвари (логгер, корс, експрессджсон )
підключити в головному файлі посилання на всі необхіні роути.
у файлі роутів прописати всі використовувані методи роутів. переважно get (2 шт), post, put(patch), delete
підключити до файлу роутів відповідні контроллери
в контролерах у обгортці асинзронної функції, яка відловлює помилку прописати функціонал асинхронної функції (шаблон)
стоврити модель даних для бази (схема + модель) експортувати для використання в контролерах і мідлварах.
Покликання:
Для фронтенду часто використовують
Для бекенду використовують