#javascript #node.js #mongodb #express #ejs
#javascript #node.js #mongodb #выразить #ejs
Вопрос:
Я пытаюсь создать простую веб-страницу, которая предлагает пользователю ввести время и нажать отправить. При отправке я хочу, чтобы данные, соответствующие этой коллекции, отображались на веб-странице.
Когда я нажимаю на кнопку отправки. Это экономит время, которое я вставил в соответствующую коллекцию Mongo. На самом деле, у меня даже есть консоль.ведение журнала для отображения всей коллекции. Я просто не могу заставить его отображаться на веб-странице.
Я новичок в NodeJS и MongoDB, так что потерпите меня.
Вот файл index.ejs. Clients — это имя коллекции, в котором хранятся времена.
<div>
<ul class="clients">
<% for(var i=0; i< clients.length; i ) {%>
<li class="client">
<span><%= clients[i].time %></span>
</li>
<% } %>
</ul>
</div>
</head>
<body>
<form action="/clients" method="POST">
<input type="text" placeholder="time" name="time">
<button type="submit">Submit</button>
</form>
У меня есть это в моем app.js, который успешно отправляет вставленное время в коллекцию.
app.post('/clients', (req, res) => {
var url = "mongodb://localhost:27017/lesson-data";
mongoose.connect(url, function (err, db) {
if (err) throw err;
db.collection('clients').save(req.body, (err, result) => {
if (err) return console.log(err)
console.log('saved to database')
res.redirect('/')
});
});
});
И это в моих маршрутах > index.js — который успешно регистрирует время в консоли, но не отображается на веб-странице.
router.get('/', function(req, res, next) {
var url = "mongodb://localhost:27017/lesson-data";
mongoose.connect(url, function (err, db) {
if (err) throw err;
db.collection('clients').find().toArray((err, result) => {
if (err) return console.log(err);
console.log(result );
console.log("chea");
// renders index.ejs
res.render('index', {clients: result});
});
});
});
Что я делаю не так? Я чувствую, что я близок и потратил несколько часов на попытки решить это.
Комментарии:
1. что показывает страница? ничего? вы все еще видите свою форму? Какой ответ вы получаете на вкладке «Сеть»? Кроме того, не отсутствует ли в вашем методе find параметры для поиска определенного результата?
2. Вместо этого страница перенаправляется обратно в индексный файл (страница, с которой она начинается). Итак, да, я все еще вижу свою форму
3. Ну, это поиск клиента сбора, потому что он успешно регистрирует результаты в консоли. Если он регистрируется правильно, разве он не должен отображаться в <ul> правильно?
4. вы уверены, что классы
clients
иclient
html не скрывают ul?5. можете ли вы опубликовать
console.log(result)
Ответ №1:
Итак, здесь не просто несколько ошибок, и, вероятно, лучше всего написать это как небольшое приложение с нуля, чтобы объяснить некоторые вещи.
Создание и установка зависимостей
Первое, что вы хотите сделать, это выбрать папку и создать пространство для проекта. Вам понадобится несколько вложенных папок в проекте, чтобы вы могли сделать что-то подобное через bash
, если у вас это есть:
mkdir -p ejsdemo/{models,routes,views/pages}
Если вы делаете это в Windows, то делайте все, что хотите, чтобы создать подобную структуру, но в основном вы хотите что-то подобное в этой ejs-demo
папке верхнего уровня:
.
├── models
├── routes
└── views
└── pages
Затем вы хотите инициализировать проект nodejs и установить зависимости. Вы можете сделать это снова с помощью следующего:
cd ejs-demo
npm init -y amp;amp; npm i -S express ejs mongoose morgan body-parser
Опять же, это может варьироваться в зависимости от того, какую ОС вы используете, но то, что вы хотите, — это установленная node_modules
в ejs-demo
папке и package.json
файл, который в основном читается как:
{
"name": "ejsdemo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo "Error: no test specified" amp;amp; exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"body-parser": "^1.18.3",
"ejs": "^2.6.1",
"express": "^4.16.4",
"mongoose": "^5.4.20",
"morgan": "^1.9.1"
}
}
Optionally you could just create the package.json
based on that content within the folder and run npm i
which is basically short for npm install
and that will install everything.
Add Models
Within the models
sub-folder that should already be created, you now what to add a basic listing. The Mongoose ODM ( Object Document Mapper ) actually has a concept of registering «models» for your collections which define a «schema» and can also enforce other validation constraints or even special instance methods or «static» class methods for special purposes.
Think of these as «wrappers» for you collection, that actually include helpers for a lot of common actions and reduce boilerplate. We are just using a very simple model for demonstration here that we will name as:
models/client.js
const { Schema } = mongoose = require('mongoose');
const clientSchema = new Schema({
name: String,
time: String
});
module.exports = mongoose.model('Client', clientSchema);
This is very basic, and simply imports the Schema
helper function for defining a «schema» which is used with the mongoose.model()
function that actually registers the model.
This is all that needs to go in this «module», and we will require()
this same file in other modules where we want to use this model. Note that we don’t need to know about a connection here.
Add Routes
Typically you would want to abstract the route handlers from the main application logic, and there is a simple way to do that. Following your example we will create two routes within modules which we will again require()
in the appropriate place:
routes/root.js
const express = require('express');
const router = express.Router();
const Client = require('../models/client');
router.get('/', async (req, res, next) => {
try {
let clients = await Client.find();
console.log(clients);
res.render('pages/index', { clients });
} catch (e) {
next(e);
}
});
module.exports = router;
routes/clients.js
const express = require('express');
const router = express.Router();
const Client = require('../models/client');
router.post('/', async (req, res, next) => {
try {
console.log(req.body);
await Client.create(req.body);
res.redirect('/');
} catch (e) {
next(e);
}
});
module.exports = router;
Both of these are again very simple examples. Note how they both import the Client
from the model created earlier. Both also have one method being GET and POST respectively and are tried to a «root» path. This will be a relative route to the final endpoint which will be registered later. But such a structure allows addition of «sub-routes» and other Http «verb» actions to be defined.
I’m demonstrating these all using async/await
from NodeJS 8.x and greater. If you are learning then this should be the minimal version you are running on. You can optionally use callbacks or plain promises if it suits your style, but the modern async/await
syntax will generally lead to cleaner and easier to read code that your peers will thank you for.
Very simple calls to either .find()
or .create()
from the model in either case, which are simply ‘awaited’ using await
as they each return a Promise and you can do so. Note the async
before the definition of each function handler. This is required to mark the block as async
before you can await
on a result.
.find()
Конечно, это просто возврат всех данных в коллекции, и поскольку это метод mongoose в модели, он возвращается уже как Array
для удобства. Также .create()
это в основном оболочка insertOne()
, которая при необходимости может выполнять итерации по массиву документов для создания и которая по существу «сохраняет» в коллекции. Это просто использование req.body
, которое к моменту фактического вызова этого маршрута будет содержать объект JavaScript с некоторым «опубликованным» содержимым формы.
Добавление представлений
Также вам необходимо настроить шаблоны просмотра. Опять же, это может быть связано, но для простой демонстрации мы просто будем использовать один базовый шаблон, аналогичный тому, что в вопросе:
просмотры / страницы /index.ejs
<div>
<ul class="clients">
<% for ( let client of clients ) { %>
<li class="client">
<span><%= client.name %>
<span><%= client.time %>
</li>
<% } %>
</ul>
</div>
<form action="/clients" method="POST">
<input type="text" placeholder="name" name="name">
<input type="text" placeholder="time" name="time">
<div>
<button type="submit">Submit</button>
</div>
</form>
Я даже не беспокоюсь о стилизации или любой другой структуре HTML-оболочки. Простой список и форма достаточно хороши для демонстрации. Также обратите внимание на современный for..of
цикл, который намного чище, чем обращение к элементам массива по индексу. EJS в основном поддерживает JavaScript в шаблонах. Итак, если это допустимый JavaScript, то он допустим для использования в шаблоне. В разумных пределах:
Основное приложение
Все, что в основном осталось, — это основной index.js
файл, который нужно поместить в корневую папку проекта. На самом деле все, что мы собираемся здесь сделать, это загрузить некоторые из модулей, которые мы создали ранее, зарегистрировать конечные точки, настроить подключение к базе данных и запустить http-прослушиватель. В основном это последовательный процесс, но мы можем выполнить некоторые вещи:
index.js
const mongoose = require('mongoose');
const express = require('express');
const morgan = require('morgan');
const bodyParser = require('body-parser');
const Client = require('./models/client');
const rootRoutes = require('./routes/root');
const clientRoutes = require('./routes/clients');
const uri = 'mongodb://localhost:27017/lesson-test';
const opts = { useNewUrlParser: true };
mongoose.set('useFindAndModify', false);
mongoose.set('useCreateIndex', true);
mongoose.set('debug', true);
const app = express();
app.set('view engine', 'ejs');
app.use(morgan('combined'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use('/', rootRoutes);
app.use('/clients', clientRoutes);
(async function() {
try {
const conn = await mongoose.connect(uri, opts);
// Clean data for demo
await Promise.all(
Object.entries(conn.models).map(([k, m]) => m.deleteMany())
);
// Insert some starter sample
await Client.insertMany([
{ name: 'One', time: '2:00' },
{ name: 'Two', time: '3:00' }
]);
app.listen(3000);
} catch (e) {
console.error(e)
}
})()
В самом верху списка находится всего лишь блок, который требуется в основных модулях, которые мы установили ранее при инициализации проекта. Это включает, mongoose
конечно, поскольку мы хотим использовать connect()
MongoDB и express
поскольку нам нужно настроить основные обработчики приложения. Другие вещи, подобные morgan
, существуют только для того, чтобы показать некоторое «протоколирование» в консоли, подтверждающее запросы, и bodyParser
что очень важно, поскольку нам нужно декодировать POST-запрос из формы позже.
Следующая часть:
const Client = require('./models/client');
const rootRoutes = require('./routes/root');
const clientRoutes = require('./routes/clients');
Это просто импорт «модулей», которые мы создали ранее. Обычно вам не нужны Client
или другие модели в такого рода index.js
списке, но для этой демонстрации мы собираемся настроить некоторые данные, готовые к первому запросу. Другие импортируют обработчики маршрутов, которые мы настроили ранее.
Следующая часть списка на самом деле просто настроена для mongoose и в основном необязательна. Единственными реальными вещами, имеющими значение здесь, являются uri
и opts
настройки, которые предназначены для фактического подключения. Они находятся в верхней части списка примеров на случай, если uri
потребуется внести изменения для вашего подключения к MongoDB. Обратите внимание, что демонстрация является «автономной», поэтому НЕ указывайте это на какую-либо существующую базу данных, поскольку она ожидает имя, которое в противном случае не используется.
Затем идет экспресс-настройка:
app.set('view engine', 'ejs');
app.use(morgan('combined'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use('/', rootRoutes);
app.use('/clients', clientRoutes);
Первая строка регистрируется ejs
для шаблонов без каких-либо других настроек, поэтому местоположения по умолчанию используются в местах, которые мы уже определили. morgan
Строка настраивает промежуточное программное обеспечение для регистрации запросов, как и два bodyParser
вызова, также регистрирующие соответствующее промежуточное программное обеспечение для синтаксического анализа JSON и содержимого в кодировке URLEND, где последнее используется по умолчанию для сообщений HTML-формы.
Последние две строки берут эти импортированные данные для обработчиков маршрута и назначают их конечным точкам. Вот почему в самих определениях используются оба обработчика запросов, /
поскольку это относится к конечным точкам, определенным здесь в разделе app.use()
. Это довольно распространенная практика.
Далее идет основной блок кода, который опять же довольно прост:
(async function() {
try {
const conn = await mongoose.connect(uri, opts);
// Clean data for demo
await Promise.all(
Object.entries(conn.models).map(([k, m]) => m.deleteMany())
);
// Insert some starter sample
await Client.insertMany([
{ name: 'One', time: '2:00' },
{ name: 'Two', time: '3:00' }
]);
app.listen(3000);
} catch (e) {
console.error(e)
}
})()
Note the block is marked async
so we can use the await
keyword within it. Also there is the same try..catch
block style for error handling. The simple first call within this actually connects to MongoDB. This is the first actual asynchronous method call in the running application. So you await
it before we go any further in the code execution. It is just taking the uri
and opts
arguments defined earlier.
Since this is a «self contained» demonstration, I’m just emptying the target collections from all registered models before we do anything else. Not the sort of thing you would typically do but the Promise.all( Object.entries(..).map(..) )
thing is basically a way of processing something for every registered model with mongoose. That «registration» happens in the initial require()
for any model as shown near the top of the listing.
The next thing should be pretty obvious as we are just using Client.insertMany()
to insert some sample data to start with. Again this is an asynchronous function so you await
the result before continued execution.
Finally we should be happy that we are connected to MongoDB and have inserted some sample data to start with, so it’s okay to start listening for requests on port 3000
of localhost
as the default.
Running the Application
If you have all of this in place then the directory structure should now look something like this ( omitting all details under node_modules
of course ):
.
├── index.js
├── models
│ └── client.js
|── node_modules
├── package.json
├── package-lock.json
├── routes
│ ├── clients.js
│ └── root.js
└── views
└── pages
└── index.ejs
If it’s like that and keeping the exact same code as presented above then it’s ready to run with:
node index.js
You should then see these lines appear:
Mongoose: clients.deleteMany({}, {})
Mongoose: clients.insertMany([ { _id: 5ca06fbc38a9b536315d732c, name: 'One', time: '2:00', __v: 0 }, { _id: 5ca06fbc38a9b536315d732d, name: 'Two', time: '3:00', __v: 0 } ], {})
Now you should be ready to open your browser to http://localhost:3000/
and view the rendered template assigned to that route. The console where you ran the application should indicate that the route has been hit:
Mongoose: clients.find({}, { projection: {} })
[ { _id: 5ca06fbc38a9b536315d732c,
name: 'One',
time: '2:00',
__v: 0 },
{ _id: 5ca06fbc38a9b536315d732d,
name: 'Two',
time: '3:00',
__v: 0 } ]
::ffff:10.0.2.2 - - [31/Mar/2019:07:45:26 0000] "GET / HTTP/1.1" 200 423 "-" "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36"
And of course that also shows the request from Mongoose to the MongoDB server. The same data should now be rendered within the <li>
items on the page.
Вы также можете заполнить поля формы и отправить, что должно показать в консоли ответ типа:
{ name: 'Four', time: '4:00' }
Mongoose: clients.insertOne({ _id: ObjectId("5ca0710038a9b536315d732e"), name: 'Four', time: '4:00', __v: 0 })
::ffff:10.0.2.2 - - [31/Mar/2019:07:49:20 0000] "POST /clients HTTP/1.1" 302 46 "http://localhost:3000/" "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36"
Который показывает проанализированное req.body
содержимое и результирующий insertOne()
результат create()
метода модели, и, конечно, запись POST
запроса. Затем действие перенаправления приведет обратно к /
маршруту:
Mongoose: clients.find({}, { projection: {} })
[ { _id: 5ca06fbc38a9b536315d732c,
name: 'One',
time: '2:00',
__v: 0 },
{ _id: 5ca06fbc38a9b536315d732d,
name: 'Two',
time: '3:00',
__v: 0 },
{ _id: 5ca0710038a9b536315d732e,
name: 'Four',
time: '4:00',
__v: 0 } ]
::ffff:10.0.2.2 - - [31/Mar/2019:07:49:20 0000] "GET / HTTP/1.1" 200 504 "http://localhost:3000/" "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36"
Заключение
И это основные концепции, которые вам нужно использовать и повторить в ваших собственных приложениях. Основные вещи, которые мы рассмотрели здесь, были:
-
Создание моделей — где вы определяете модель для каждой коллекции, что позволяет вам устанавливать правила для схемы. Mongoose необязательно может быть установлен в
{ strict: false }
и вообще не вызывать проверку схемы или преобразование типа. Как правило, это немного более удобно, чем работа с основными методами драйвера. -
Отдельные маршруты — Действия и обработчики могут быть настроены в логических группах для того, где они должны быть, без привязки к строгой конечной точке. Настройка возможных конечных точек может быть выполнена позже, и этот интерфейс «контроллера» на самом деле является всего лишь «уровнем рукопожатия» между представлением презентации и моделями.
-
Подключайтесь к базе данных ОДИН РАЗ — это важное правило, и оно услужливо соблюдается общей схемой использования Mongoose. Ваше приложение, основанное на запросе, не имеет бизнес-подключения и отключения в рамках каждого запроса (как вы делали). Вы подключаетесь только ОДИН раз и оставляете это открытым. Драйвер фактически управляет такими вещами, как объединение в пул соединений и помогает распределять, поэтому несколько одновременных запросов не блокируются.
Также любые дополнительные подключения в пуле будут управляться драйвером и отключаться, когда они не нужны. Хотя обычно размер пула по умолчанию остается открытым, он всегда готов к следующему запросу. Как правило, вам не следует беспокоиться об этом на данном этапе, поскольку это детали, о которых вам нужно узнать только тогда, когда вы действительно столкнетесь с необходимостью знать. И этого не будет некоторое время.
В принципе, если вы будете следовать всему приведенному здесь, то у вас будет рабочий пример того, что вы в основном пытались сделать, и то, что вы можете «развить», чтобы сделать больше и лучше.
Получайте удовольствие!