В этом пошаговом руководстве мы создадим приложение для управления отзывами, от бэкенда до внешнего интерфейса, используя ReactJS для внешнего интерфейса и NodeJS, Nodemailer и Gmail для внутреннего интерфейса.
Содержание
- Что мы строим
- Какие пакеты мы используем
- Конфигурация Gmail
- Давайте код (Back-end)
- Давайте код (Front-end)
- Запуск вашего внутреннего приложения на машине Linux с использованием pm2
- Полный полный исходный код
- Заключение
Что мы строим
- Мы создадим приложение с формой, в которой пользователь может указать свое имя, адрес электронной почты и свой отзыв.
- Данные формы будут отправлены в серверное приложение NodeJS через API.
- Затем данные будут отформатированы в HTML-страницу, а затем повторно отправлены в учетную запись Gmail с помощью Nodemailer.
- Чтобы сохранить нашу архитектуру кода чистой, мы сгруппируем каждую функциональность в отдельный файл.
- Мы научимся управлять cors в отдельном файле со специальной конфигурационной папкой «/config».
- Мы будем управлять проверками в другом файле: «validations/feedback.js».
- Все наши функции, связанные с маршрутом «/feedback», также будут управляться в отдельном файле: «/controllers/feedbackController.js».
- Для блокировки спама будет использоваться ограничитель скорости (5 отзывов в час): «/middlewares/rateLimiter.js».
- Мы запустим нашу серверную часть на машине с Linux, используя pm2, это позволит нам перезапуститься в случае сбоя нашего приложения или перезагрузки сервера и вести журналы для различных событий.
Какие пакеты мы используем
Для задней части:
- Мы будем использовать NodeJS и Express для управления маршрутом API.
- Dotenv: для управления переменными окружения.
- express-rate-limit: чтобы ограничить количество отзывов, которые пользователь может отправить в наш API (механизм защиты от спама).
- cors: для управления cors в нашем внутреннем приложении.
- Nodemailer: для отправки писем на учетную запись Gmail.
Для фронтенда:
- Мы будем использовать ReactJS для создания простой формы связи с нами с полями форта: имя, электронная почта, тема, сообщение.
Поскольку мы отправляем электронную почту из нашего серверного модуля, все наши конфигурации Gmail, включая электронную почту и пароль, останутся безопасными, и конечный пользователь получит доступ к нашему приложению, используя только API.
Конфигурация Gmail
Для настройки Gmail мы будем использовать концепцию менее безопасных приложений, которая означает, что любой, у кого есть ваш пользователь и пароль, может отправлять электронные письма с вашей учетной записи Gmail, чтобы включить менее безопасные приложения, вы можете перейти здесь.
Если вы активировали 2FA, вам нужно использовать другую концепцию: Пароль для конкретного приложения, чтобы включить его, перейдите здесь.
Давайте код (Back-end)
Мы начнем с нашего бэкэнда
Создайте новый каталог «back-end» и перейдите к нему
mkdir backend && cd backend
Инициализируйте npm, чтобы создать файл package.json для нашего внутреннего приложения, в качестве точки входа выберите: server.js
npm init
Теперь в файле «package.json» обновите раздел «скрипты».
"scripts": { "start": "node server", "dev": "node --watch server" }
Это позволит нам запускать команду node v18.x с новым флагом «-watch», чтобы отслеживать любые изменения в нашем коде (например, в пакете nodemon).
Установка пакетов npm
npm i express dotenv express-rate-limit cors nodemailer
Прежде чем перейти к основному файлу приложения (server.js), я покажу вам структуру нашего приложения, а затем мы покажем вам, как реализовать каждый пакет: express, dotenv, cors, express-rate-limit и nodmailer.
- дотенв
После установки нашего пакета добавьте эту строку в «server.js»
require("dotenv").config();
Создайте новый файл на корневом уровне нашего приложения: «.env», мы поместим сюда наши секретные переменные.
PORT=3010 [email protected] SUBJECTPREFIX=_MY_Website_ PASS=_your_gmail_password_
Не забудьте поместить сюда свои переменные и добавить этот файл в файл «.gitignore» на корневом уровне нашего приложения.
env node_modules
Вы можете вызывать любые подобные переменные: например, PORT (в нашем случае это будет 3010).
const port =process.env.PORT
- кор
У нас есть два файла конфигурации:
- разрешеноOrigins.js:
const allowedOrigins = [ "http://localhost:3000", "http://127.0.0.1:3000", "http://server_address:3000", ]; module.exports = allowedOrigins;
Этот файл содержит список разрешенных источников, любые другие источники будут отклонены.
- corsConfigs.js
const allowedOrigins = require("./allowedOrigins"); const corsConfigs = { origin: (origin, callback) => { if (allowedOrigins.indexOf(origin) !== -1 || !origin) { // remove ||!origin to block postman request callback(null, true); } else { callback(new Error("Origin not allowed by Cors")); } }, optionsSuccessStatus: 200, }; module.exports = corsConfigs;
Это файл конфигурации пакета cors (), он содержит функцию обратного вызова, которая проверяет наш файл «allowedOrigins» на наличие запроса, и если он не существует, он выдает ошибку с сообщением: «Происхождение не разрешено Cors».
« || !origin» разрешает запросы «без происхождения» из таких приложений, как postman.
Теперь включите эти две строки в наш «server.js», чтобы вызвать пакет cors.
const cors = require("cors"); const corsConfigs = require("./config/corsConfigs");
Затем передайте эти коры на наш экспресс-сервер в качестве промежуточного программного обеспечения с параметром «corsConfigs».
app.use(cors(corsConfigs));
- выражать
В нашем приложении мы создадим простой API с одним маршрутом: «http://ваш-сервер-адрес/feedback».
В наш файл «server.js» добавьте следующие строки:
const app = express(); const port = process.env.PORT || 3010; ..... app.use(express.json()); app.use("/feedback", require("./routes/feedbackRoutes")); app.listen(port, () => { console.log(`✅ Application running on port: ${port}`); });
- У нас есть запрос экспресс-пакета в первую очередь
- вызывая промежуточное ПО express.json(), при этом наше приложение может управлять форматом JSON в объекте запроса.
- Затем мы направляем все запросы из «/feedback» в наши файлы «./routes/feedbackRoutes», этот файл содержит все функции, которые управляют этим маршрутом.
- Теперь прослушайте номер порта из «.env» с 3010 по умолчанию.
- Если все пойдет хорошо, напечатайте в терминале сообщение console.log: `✅ Приложение работает на порту: ${port}`.
Наш файл «server.js» будет выглядеть так
require("dotenv").config(); const express = require("express"); const cors = require("cors"); const corsConfigs = require("./config/corsConfigs"); const app = express(); const port = process.env.PORT || 3500; app.use(cors(corsConfigs)); app.use(express.json()); app.use("/feedback", require("./routes/feedbackRoutes")); app.listen(port, () => { console.log(`✅ Application running on port: ${port}`); });
Теперь давайте объясним файл «./routes/feedbackRoutes».
const express = require("express"); const router = express.Router(); const feedbackController = require("../controllers/feedbackController"); const rateLimiter = require("../middlewares/rateLimiter"); const feedbackValidation = require("../validation/feedback"); router .route("/") .post(rateLimiter, feedbackValidation, feedbackController.feedback); module.exports = router;
В этом файле
- мы будем управлять маршрутом «/feedback» для нашего API с помощью метода: «POST»
- Мы будем использовать промежуточное программное обеспечение «rateLimiter», с ограничением количества отзывов до 5 в час.
Если это число превышено, будет отправлен запрос об ошибке n с сообщением: «Слишком много отзывов с этого IP, попробуйте позже через 1 час».
Для этого воспользуемся пакетом «express-rate-limit».
const rateLimit = require("express-rate-limit"); const rateLimiter = rateLimit({ windowMs: 60 * 60 * 1000, //1 hour limit max: 5, //5 request limit message: { message: "Too many feedbacks from this IP, please try later after 1 hour", }, handler: (req, res, next, options) => { res.status(options.statusCode).send(options.message); }, standardHeaders: true, //return rate limit info in the "RateLimit-*" headers legacyHeaders: false, //Disable the "X-RateLimit-* headers }); module.exports = rateLimiter;
- И промежуточное ПО «feedbackValidation.validate»
Мы будем управлять всеми нашими проверками в этом файле, для этого мы будем использовать пакет «express-validator».
const { body } = require("express-validator"); const validate = [ body("name").notEmpty().withMessage("You must enter a valide Name").trim(), body("email") .notEmpty() .isEmail() .withMessage("You must enter a valide email") .normalizeEmail(), body("subject") .notEmpty() .withMessage("You must enter a valide subject") .trim(), body("text") .notEmpty() .withMessage("You must enter a valide text") .isLength({ min: 10 }) .withMessage("Text must be at least 10 characters long") .trim(), ]; module.exports = validate;
Мы проверим, не пусты ли поля имени, темы и текста, действителен ли адрес электронной почты и содержит ли текстовое поле не менее 10 символов.
Если произойдет какая-либо из этих ошибок, в качестве ответа конечному пользователю будет возвращен массив с соответствующими сообщениями.
- Теперь «./controllers/feedbackController.js»
const {validationResult} = require("express-validator"); const nodemailer = require("nodemailer"); // @desc FEEDBACK // @Route POST /feedback // @Access Public const feedback = async (req, res) => { console.log(req.body); const errors = validationResult(req); if (!errors.isEmpty()) { return res.status(422).json({ errors: errors.array() }); } // NodeMailer proccessing ... let transporter = nodemailer.createTransport({ service: "gmail", auth: { user: process.env.EMAIL, pass: process.env.PASS, }, }); let mailOptions = { from: req.body.email, to: process.env.EMAIL, subject: `${process.env.SUBJECTPREFIX}: ${req.body.subject}`, html: `<div> <p>From: ${req.body.email}</p> <p>Subject: ${req.body.subject}</p> <p>${req.body.text}</p> </div>`, }; transporter.sendMail(mailOptions, function (error, info) { if (error) { console.log(error); return res.json({ message: "Error: Message is not sent" }); } else { console.log("Email sent: " + info); return res.json({ message: "Message sent with success" }); } }); }; module.exports = { feedback };
Сначала мы проверим наличие ошибок с помощью и отправим ответ с кодом состояния 422 и массивом, если какая-либо ошибка существует.
const validationResult = require("express-validator"); .... const errors = validationResult(req); if (!errors.isEmpty()) { return res.status(422).json({ errors: errors.array() }); }
Затем мы начнем использовать наш пакет Nodemailer.
. Настройте транспортер с именем пользователя и паролем из файла «.env».
let transporter = nodemailer.createTransport({ service: "gmail", auth: { user: process.env.EMAIL, pass: process.env.PASS, }, });
. Настройте электронное письмо для отправки, для этого мы будем использовать информацию из файла «req.body» и из файла «.env».
let mailOptions = { from: req.body.email, to: process.env.EMAIL, subject: `${process.env.SUBJECTPREFIX}: ${req.body.subject}`, html: `<div> <p>From: ${req.body.email}</p> <p>Subject: ${req.body.subject}</p> <p>${req.body.text}</p> </div>`, };
Наконец отправить письмо
transporter.sendMail(mailOptions, function (error, info) { if (error) { console.log(error); return res.statuscode(500).json({ message: "Error: Message is not sent" }); } else { console.log("Email sent: " + info); return res.json({ message: "Message sent with success" }); } }); };
Если все пойдет хорошо, отправьте ответ OK, в противном случае отправьте ответ об ошибке с кодом 500: Internal Server Error.
Запуск вашего внутреннего приложения на машине Linux с использованием pm2
PM2 — это менеджер производственных процессов для приложений Node.js со встроенным балансировщиком нагрузки. Это позволяет поддерживать приложения в рабочем состоянии вечно, перезагружать их без простоев и облегчать выполнение общих задач системного администратора.
Теперь мы будем запускать наше приложение в фоновом режиме, используя pm2, мы будем использовать флаг «-watch», чтобы перезапускать наше приложение при изменении кода нашего приложения.
pm2 start server.js --watch
После запуска вашего приложения вы можете легко управлять своими приложениями, запустив:
pm2 list
Мониторинг журналов
pm2 monit
Подробнее о менеджере пакетов pm2 можно прочитать здесь
Давайте код (Front-end)
Объяснив внутреннюю часть нашего приложения с полным стеком, мы не будем переходить к внешней части.
Мы будем использовать ReactJS, но вы можете использовать любой другой фреймворк или просто обычный JavaScript.
Мы построим форму с контролируемыми входами fort (с хуками useStat):
- имя: имя отправителя отзыва.
- электронная почта: электронная почта отправителя отзыва.
- subject: тема отзыва.
- text: текст или сообщение обратной связи.
Наше приложение будет содержать один компонент Form, содержащий форму и функцию отправки: handleSubmit().
- index.js
import { StrictMode } from "react"; import { createRoot } from "react-dom/client"; import App from "./App"; const rootElement = document.getElementById("root"); const root = createRoot(rootElement); root.render( <StrictMode> <App /> </StrictMode> );
- App.js
import "./styles.css"; import Form from "./Form"; export default function App() { return <Form/>; }
- Форма.js
import "./styles.css"; import { useEffect, useRef, useState } from "react"; export default function Form() { const nameRef = useRef(); const [name, setName] = useState(""); const [email, setEmail] = useState(""); const [subject, setSubject] = useState(""); const [text, setText] = useState(""); const [result, setResult] = useState(""); const handleSubmit = async (e) => { setResult(""); e.preventDefault(); // validating data if (!name || !email || !subject || text?.length < 10) { setResult("Please verify your inputs ..."); return null; } const data = { name, email, subject, text }; fetch("http://localhost:3010/feedback", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data), }) .then((response) => { return response.json(); }) .then((json) => { console.log(json?.message); setResult(`Result: ${json?.message}`); }) .catch((error) => { console.log(error?.message); setResult(`Error: ${error?.message}`); }); }; useEffect(() => { nameRef.current.focus(); }, []); return ( <div className="App"> <h1>Fomr2Email - Feedback</h1> <form onSubmit={handleSubmit} className="form__container"> <div className="form__controls"> <label htmlFor="name">Name</label> <input ref={nameRef} type="text" id="name" value={name} onChange={(e) => setName(e.target.value)} /> </div> <div className="form__controls"> <label htmlFor="email">Email</label> <input id="email" type="email" value={email} onChange={(e) => setEmail(e.target.value)} /> </div> <div className="form__controls"> <label htmlFor="subject">Subject</label> <input id="subject" className="input__subject" type="text" value={subject} onChange={(e) => setSubject(e.target.value)} /> </div> <div className="form__controls"> <label htmlFor="text">Text</label> <textarea rows="5" id="text" type="text" value={text} onChange={(e) => setText(e.target.value)} /> </div> <div className="form__controls"> <button className="button">Send Feedback</button> </div> </form> <p>{result}</p> </div> ); }
Как вы можете видеть в функции handleSubmit()
- Сброс результата
setResult("");
- Простая проверка данных
// validating data if (!name || !email || !subject || text?.length < 10) { setResult("Please verify your inputs ..."); return null; }
- Получение данных
const data = { name, email, subject, text }; fetch("http://localhost:3010/feedback", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data), }) .then((response) => { return response.json(); }) .then((json) => { console.log(json?.message); setResult(`Result: ${json?.message}`); }) .catch((error) => { console.log(error?.message); setResult(`Error: ${error?.message}`); }); };
Результат выборки будет сохранен в статистике «результат» с «setResult», а затем показан в конце формы.
<p>{result}</p>
Прежде чем перейти к стилю, просто взгляните на эту функцию.
const nameRef = useRef(); ... useEffect(() => { nameRef.current.focus(); }, []);
Когда наш компонент монтируется, ввод имени получает фокус.
- стили.css
.App { font-family: sans-serif; text-align: center; display: flex; flex: 1; flex-direction: column; } .button { height: 40px; width: 150px; font-size: 1.2rem; border-radius: 5px; cursor: pointer; transition: 0.1s ease-in-out; margin: auto; } .button:hover { background-color: #84c586; color: whitesmoke; border-color: gray; transform: scale(1.1); } .form__controls { display: flex; flex-direction: row; justify-content: flex-start; align-items: flex-start; margin: 10px; padding: 2px; } .form__controls > label { margin-right: 10px; width: 100px; height: 30px; } .form__controls > input { width: 200px; height: 30px; border-radius: 5px; border: 1px solid gray; transition: 0.1s ease-in-out; } .form__controls > textarea { width: 300px; border-radius: 5px; border: 1px solid gray; transition: 0.1s ease-in-out; } .form__controls > input:focus, textarea:focus { outline: 2px solid #84c586; outline-offset: 1px; transform: scale(1.05); } .form__container { border: 1px solid #ccc; border-radius: 5px; margin: auto; padding: 10px; width: 450px; } @media only screen and (max-width: 480px) { .form__container { width: 290px; } .form__controls > input { width: 150px; } .form__controls > textarea { width: 280px; } }
Стилизация формы
Полный полный исходный код
Заключение
С Form2Email вам больше никогда не понадобятся сторонние веб-сервисы для включения форм обратной связи или обратной связи.
Его легко реализовать, с любым внешним рабочим процессом, событием для внутреннего интерфейса, это крошечный скрипт (всего 26,1 МБ памяти), который вы можете запустить на старом ПК с любой системой (способной запускать NodeJS).
Спасибо за прочтение.
Дополнительные материалы на PlainEnglish.io.
Подпишитесь на нашу бесплатную еженедельную рассылку новостей. Подпишитесь на нас в Twitter, LinkedIn, YouTube и Discord .
Заинтересованы в масштабировании запуска вашего программного обеспечения? Ознакомьтесь с разделом Схема.