Membangun Aplikasi Web Menggunakan Node.js
Indonesian (Bahasa Indonesia) translation by Meyria (you can also view the original English article)
Pengenalan
Selain membangun API, Node.js sangat bagus untuk membangun aplikasi web standar. Ini memiliki alat yang hebat untuk memenuhi selera pengembang web. Dalam tutorial ini, Anda akan membangun aplikasi web yang bisa berfungsi sebagai perpustakaan lokal.
Sementara membangun Anda akan belajar tentang beberapa jenis middleware, Anda akan melihat bagaimana menangani pengiriman form di Node.js, dan Anda juga bisa merujuk dua model.
Mari memulai.
Memulai
Mulailah dengan memasang generator express pada mesin anda.
1 |
npm install express-generator -g |
Jalankan perintah generator express untuk menghasilkan aplikasi anda.
1 |
express tutsplus-library --view=pug |
1 |
create : tutsplus-library |
2 |
create : tutsplus-library/package.json |
3 |
create : tutsplus-library/app.js |
4 |
create : tutsplus-library/public |
5 |
create : tutsplus-library/routes |
6 |
create : tutsplus-library/routes/index.js |
7 |
create : tutsplus-library/routes/users.js |
8 |
create : tutsplus-library/views |
9 |
create : tutsplus-library/views/index.pug |
10 |
create : tutsplus-library/views/layout.pug |
11 |
create : tutsplus-library/views/error.pug |
12 |
create : tutsplus-library/bin |
13 |
create : tutsplus-library/bin/www |
14 |
create : tutsplus-library/public/javascripts |
15 |
create : tutsplus-library/public/images |
16 |
create : tutsplus-library/public/stylesheets |
17 |
create : tutsplus-library/public/stylesheets/style.css |
18 |
|
19 |
install dependencies:
|
20 |
$ cd tutsplus-library && npm install |
21 |
|
22 |
run the app:
|
23 |
$ DEBUG=tutsplus-library:* npm start |
Sekarang bermigrasi ke pekerjaan Anda, buka package.json, dan buat dependensi yang sama dengan yang saya miliki di bawah ini.
1 |
#package.json |
2 |
|
3 |
{
|
4 |
"name": "tutsplus-library", |
5 |
"version": "0.0.0", |
6 |
"private": true, |
7 |
"scripts": { |
8 |
"start": "node ./bin/www" |
9 |
},
|
10 |
"dependencies": { |
11 |
"body-parser": "~1.17.1", |
12 |
"connect-flash": "^0.1.1", |
13 |
"cookie-parser": "~1.4.3", |
14 |
"debug": "~2.6.3", |
15 |
"express": "~4.15.2", |
16 |
"express-messages": "^1.0.1", |
17 |
"express-session": "^1.15.5", |
18 |
"express-validator": "^4.2.1", |
19 |
"mongoose": "^4.11.12", |
20 |
"morgan": "~1.8.1", |
21 |
"pug": "~2.0.0-beta11", |
22 |
"serve-favicon": "~2.4.2" |
23 |
}
|
24 |
}
|
Jalankan perintah untuk menginstal paket.
1 |
npm install
|
Siapkan file entri
app.js dibuat saat Anda menjalankan perintah generator; Namun, Anda perlu mengatur konfigurasi ekstra. Edit file agar terlihat seperti yang saya miliki di bawah ini.
1 |
#app.js |
2 |
|
3 |
var express = require('express'); |
4 |
var path = require('path'); |
5 |
var favicon = require('serve-favicon'); |
6 |
var logger = require('morgan'); |
7 |
var cookieParser = require('cookie-parser'); |
8 |
var bodyParser = require('body-parser'); |
9 |
const session = require('express-session') |
10 |
const expressValidator = require('express-validator') |
11 |
const flash = require('connect-flash') |
12 |
const mongoose = require('mongoose') |
13 |
|
14 |
// 1
|
15 |
const genres = require('./routes/genres'); |
16 |
const books = require('./routes/books'); |
17 |
|
18 |
var app = express(); |
19 |
|
20 |
// 2
|
21 |
mongoose.Promise = global.Promise |
22 |
const mongoDB = process.env.MONGODB_URI || 'mongodb://127.0.0.1/tutsplus-library' |
23 |
mongoose.connect(mongoDB) |
24 |
|
25 |
// view engine setup
|
26 |
app.set('views', path.join(__dirname, 'views')); |
27 |
app.set('view engine', 'pug'); |
28 |
|
29 |
// uncomment after placing your favicon in /public
|
30 |
//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
|
31 |
app.use(logger('dev')); |
32 |
app.use(bodyParser.json()); |
33 |
app.use(bodyParser.urlencoded({ extended: false })); |
34 |
app.use(cookieParser()); |
35 |
app.use(express.static(path.join(__dirname, 'public'))); |
36 |
|
37 |
// 3
|
38 |
app.use(session({ |
39 |
secret: 'secret', |
40 |
saveUninitialized: true, |
41 |
resave: true |
42 |
}))
|
43 |
|
44 |
// 4
|
45 |
app.use(expressValidator({ |
46 |
errorFormatter: function(param, msg, value) { |
47 |
var namespace = param.split('.') |
48 |
, root = namespace.shift() |
49 |
, formParam = root |
50 |
|
51 |
while(namespace.length) { |
52 |
formParam += '[' + namespace.shift() + ']' |
53 |
}
|
54 |
return { |
55 |
param : formParam, |
56 |
msg : msg, |
57 |
value : value |
58 |
}
|
59 |
}
|
60 |
}))
|
61 |
|
62 |
// 5
|
63 |
app.use(flash()) |
64 |
app.use(function (req, res, next) { |
65 |
res.locals.messages = require('express-messages') |
66 |
next() |
67 |
})
|
68 |
|
69 |
// 6
|
70 |
app.use('/genres', genres); |
71 |
app.use('/books', books); |
72 |
|
73 |
// catch 404 and forward to error handler
|
74 |
app.use(function(req, res, next) { |
75 |
var err = new Error('Not Found'); |
76 |
err.status = 404; |
77 |
next(err); |
78 |
});
|
79 |
|
80 |
// error handler
|
81 |
app.use(function(err, req, res, next) { |
82 |
// set locals, only providing error in development
|
83 |
res.locals.message = err.message; |
84 |
res.locals.error = req.app.get('env') === 'development' ? err : {}; |
85 |
|
86 |
// render the error page
|
87 |
res.status(err.status || 500); |
88 |
res.render('error'); |
89 |
});
|
90 |
|
91 |
module.exports = app; |
- Anda membutuhkan dua route yang akan Anda gunakan dalam membangun aplikasi ini. Anda akan segera membuat route file. Rute yang diperlukan ditetapkan sebagai nilai pada dua variabel berbeda yang digunakan saat menyiapkan middleware untuk route Anda.
- Anda menetapkan Mongoose untuk menggunakan
global.Promise. VariabelMongoDBditugaskanMONGODB_URIuntuk lingkungan kerja anda atau jalan ke server mongo lokal anda. Variabel ini dilewatkan sebagai argumen untuk terhubung ke server MongoDB yang sedang berjalan. - Anda menyiapkan session middleware menggunakan
express-session. Middleware ini penting karena Anda akan menampilkan pesan sekejap di beberapa bagian aplikasi anda. - Anda menyiapkan middleware untuk validasi. Middleware ini akan digunakan untuk memvalidasi form input, memastikan pengguna aplikasi tidak mengirimkan form kosong. Validasi menggunakan paket yang terinstal,
express-validator. - Anda menyiapkan middleware yang akan berguna saat menampilkan pesan kilat. Middleware ini menggunakan
connect-flash. - Route untuk aplikasi disiapkan untuk memanfaatkan file route yang anda butuhkan. Permintaan yang menunjuk ke /genres dan /books akan menggunakan genre dan buku yang mengarahkan file masing-masing. Pada saat ini Anda belum membuat file route, namun anda akan segera melakukannya.
Model Buku dan Genre
Model Buku akan menggunakan Skema Mongoose untuk menentukan bagaimana buku akan terstruktur. Buat direktori yang disebut models, dan sebuah file baru bernama Book.js. Begini tampilannya.
1 |
#models/Book.js |
2 |
|
3 |
const mongoose = require('mongoose') |
4 |
mongoose.Promise = global.Promise |
5 |
const Schema = mongoose.Schema |
6 |
|
7 |
const bookSchema = Schema({ |
8 |
name: { |
9 |
type: String, |
10 |
trim: true, |
11 |
required: 'Please enter a book name' |
12 |
},
|
13 |
description: { |
14 |
type: String, |
15 |
trim: true |
16 |
},
|
17 |
author: { |
18 |
type: String, |
19 |
trim: true, |
20 |
},
|
21 |
genre: [{ |
22 |
type: Schema.Types.ObjectId, |
23 |
ref: 'Genre' |
24 |
}]
|
25 |
})
|
26 |
|
27 |
module.exports = mongoose.model('Book', bookSchema) |
Di sini anda memiliki empat field. Field terakhir digunakan untuk menyimpan genre yang dimiliki setiap buku. Bidang genre di sini merujuk pada model Genre, yang akan dibuat selanjutnya. Itu sebabnya tipe ini diatur ke Schema.Types.ObjectId, di mana id dari masing-masing genre yang dirujuk akan disimpan. ref menentukan model yang Anda referensikan. Perhatikan bahwa genre disimpan sebagai array, artinya buku bisa memiliki lebih dari satu genre.
Mari kita segera membuat model Genre.
1 |
#models/genre.js |
2 |
|
3 |
const mongoose = require('mongoose') |
4 |
mongoose.Promise = global.Promise |
5 |
const Schema = mongoose.Schema |
6 |
|
7 |
const genreSchema = Schema({ |
8 |
name: { |
9 |
type: String, |
10 |
trim: true, |
11 |
required: 'Please enter a Genre name' |
12 |
}
|
13 |
})
|
14 |
|
15 |
module.exports = mongoose.model('Genre', genreSchema) |
Untuk Genre Anda, Anda hanya perlu satu field: nama.
Route Genre Index dan View
Untuk tutorial ini, Anda akan menggunakan dua jalur route untuk genre anda: jalur untuk menambahkan genre baru, dan satu lagi yang mencantumkan genre yang anda miliki. Buat file di direktori route Anda yang disebut genres.js.
Mulailah dengan membutuhkan semua modul yang akan anda gunakan.
1 |
#routes/genres.js |
2 |
|
3 |
var express = require('express'); |
4 |
var router = express.Router(); |
5 |
const mongoose = require('mongoose') |
6 |
const Genre = require('../models/Genre') |
Selanjutnya, turunkan route yang menangani file index untuk genre anda.
1 |
router.get('/', (req, res, next) => { |
2 |
const genres = Genre.find({}).exec() |
3 |
.then((genres) => { |
4 |
res.render('genres', { genres: genres }) |
5 |
}, (err) => { |
6 |
throw err |
7 |
})
|
8 |
});
|
Route ini akan dipanggil kapan pun permintaan diajukan ke /genres. Di sini Anda memanggil metode pencarian pada model Genre Anda untuk mendapatkan semua genre yang telah dibuat. Genre ini kemudian di-render pada template yang disebut genres. Ayo maju dan buat itu, tetapi pertama, perbarui layout.pug anda agar terlihat seperti ini:
1 |
#views/layout.pug |
2 |
|
3 |
doctype html |
4 |
html |
5 |
head |
6 |
title= title |
7 |
link(rel='stylesheet', href='/stylesheets/style.css') |
8 |
link(rel='stylesheet', href='https://bootswatch.com/paper/bootstrap.css') |
9 |
script(src='https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js') |
10 |
script(src='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js') |
11 |
body |
12 |
.container-fluid |
13 |
block header |
14 |
nav.navbar.navbar-inverse |
15 |
.container-fluid |
16 |
.navbar-header |
17 |
button.navbar-toggle.collapsed(type='button', data-toggle='collapse', data-target='#bs-example-navbar-collapse-2') |
18 |
span.sr-only Toggle navigation |
19 |
span.icon-bar |
20 |
span.icon-bar |
21 |
span.icon-bar |
22 |
a.navbar-brand(href='#') Local Library |
23 |
#bs-example-navbar-collapse-2.collapse.navbar-collapse |
24 |
ul.nav.navbar-nav.navbar-right |
25 |
li |
26 |
a(href='/books') View Books |
27 |
li |
28 |
a(href='/books/add') Add New Book |
29 |
li |
30 |
a(href='/genres') View Genres |
31 |
li |
32 |
a(href='/genres/add') Add New Genre |
33 |
block content |
Ini akan memberi anda pandangan struktur yang bagus untuk membantu navigasi. Sekarang buat file view bernama genre.pug. Dalam file ini, anda akan melakukan perulangan melalui genre yang dibuat dan menampilkan setiap genre dalam daftar yang tidak berurutan.
Berikut adalah bagaimana file akan terlihat.
1 |
#views/genres.pug |
2 |
|
3 |
extends layout |
4 |
|
5 |
block content |
6 |
h1 Genre |
7 |
ul.well.well-lg |
8 |
each genre, i in genres |
9 |
li.well.well-sm |
10 |
p #{genre.name}
|
Tambahkan Route Genre Baru dan View
Kembali ke routes/genres.js untuk menambahkan route yang akan menangani pembuatan genre baru.
1 |
#routes/genres.js |
2 |
|
3 |
// 1
|
4 |
router.get('/add', (req, res, next) => { |
5 |
res.render('addGenre') |
6 |
})
|
7 |
|
8 |
// 2
|
9 |
router.post('/add', (req, res, next) => { |
10 |
req.checkBody('name', 'Name is required').notEmpty() |
11 |
|
12 |
const errors = req.validationErrors() |
13 |
|
14 |
if (errors) { |
15 |
console.log(errors) |
16 |
res.render('addgenres', { genre, errors }) |
17 |
}
|
18 |
|
19 |
const genre = (new Genre(req.body)).save() |
20 |
.then((data) => { |
21 |
res.redirect('/genres') |
22 |
})
|
23 |
.catch((errors) => { |
24 |
console.log('oops...') |
25 |
console.log(errors) |
26 |
})
|
27 |
})
|
28 |
|
29 |
// 3
|
30 |
module.exports = router; |
- Tugas router ini adalah dengan hanya menampilkan halaman untuk menambahkan route baru. Router ini dipanggil setiap kali permintaan dibuat ke jalur /genres/add.
- Router ini menangani pengajuan form. Saat form dikirimkan, kitai periksa untuk memastikan bahwa sebuah nama dimasukkan oleh pengguna. Jika tidak ada nama yang dimasukkan, halaman akan dirender-ulang. Jika diperiksa bagus, genre akan disimpan dan pengguna dialihkan ke halaman /genres.
- Modul ini diekspor sebagai sebuah router.
Sekarang snda bisa terus maju dan membuat halaman untuk menambahkan genre baru.
1 |
#views/addGenre.pug |
2 |
|
3 |
extends layout |
4 |
|
5 |
block content |
6 |
.row |
7 |
.col-md-12 |
8 |
h1 Add Book |
9 |
form(method="POST", action="/genres/add") |
10 |
.form-group |
11 |
label.col-lg-2.control.label Name |
12 |
.col-lg-10 |
13 |
input.form-control(type="text", name='name') |
14 |
.form-group |
15 |
.col-lg-10.col-lg-offset-2 |
16 |
input.button.btn.btn-primary(type='submit', value='Submit') |
17 |
|
18 |
if errors |
19 |
ul |
20 |
for error in errors |
21 |
li!= error.msg |
Route Buku dan View
Buat sebuah file route baru untuk buku, dan nama diberi nama books.js. Seperti yang anda lakukan sebelumnya dengan genre, mulailah dengan membutuhkan modul-modul yang diperlukan.
1 |
#routes/books.js |
2 |
|
3 |
var express = require('express'); |
4 |
var router = express.Router(); |
5 |
const mongoose = require('mongoose') |
6 |
const Book = require('../models/Book') |
7 |
const Genre = require('../models/Genre') |
Selanjutnya, mengatur router untuk menampilkan semua buku yang disimpan di Perpustakaan. Coba itu sendiri seperti anda mengaturnya pada genre; anda selalu dapat memeriksa kembali untuk melakukan koreksi.
Saya kira anda akan mencobanya—berikut adalah bagaimana hal ini akan terlihat.
1 |
router.get('/', (req, res, next) => { |
2 |
const books = Book.find({}).exec().then((books) => { |
3 |
res.render('books', { books: books }) |
4 |
}, (err) => { |
5 |
throw err |
6 |
})
|
7 |
});
|
Ketika router ini dipanggil, permintaan dibuat untuk menemukan semua buku-buku yang disimpan dalam database. Jika semuanya berjalan lancar, buku-buku akan ditampilkan pada halaman /books, jika tidak sebuah error akan muncul.
Anda perlu membuat file baru untuk menampilkan semua buku, dan berikut adalah bagaimana hal ini akan terlihat.
1 |
#views/books.pug |
2 |
|
3 |
extends layout |
4 |
|
5 |
block content |
6 |
h1 Books |
7 |
ul.well.well-lg |
8 |
each book, i in books |
9 |
li.well.well-sm |
10 |
a(href=`/books/show/${book.id}`) #{book.name}
|
11 |
p= book.description |
Anda cukup melakukan perulangan melalui buku-buku yang dikembalikan dan mengeluarkan nama dan deskripsi dari setiap buku yang menggunakan daftar yang tidak berurutan. Nama buku mengarah ke halaman masing-masing buku.
Route Tambah Buku Baru dan View
Pada router berikutnya anda akan mengatur penanganan penambahan buku baru. Kedua router akan digunakan di sini: yang satu hanya akan membuat halaman, dan yang lain akan menangani pengajuan form.
Ini adalah bagaimana router terlihat.
1 |
router.get('/add', (req, res, next) => { |
2 |
const genres = Genre.find({}).exec() |
3 |
.then((genres) => { |
4 |
res.render('addBooks', { genres }) |
5 |
})
|
6 |
.catch((err) => { |
7 |
throw err |
8 |
})
|
9 |
})
|
10 |
|
11 |
router.post('/add', (req, res, next) => { |
12 |
req.checkBody('name', 'Name is required').notEmpty() |
13 |
req.checkBody('description', 'Description is required').notEmpty() |
14 |
req.checkBody('genre', 'Genre is required').notEmpty |
15 |
|
16 |
const errors = req.validationErrors() |
17 |
|
18 |
if (errors) { |
19 |
console.log(errors) |
20 |
res.render('addBooks', { book, errors }) |
21 |
}
|
22 |
|
23 |
const book = (new Book(req.body)).save() |
24 |
.then((data) => { |
25 |
res.redirect('/books') |
26 |
})
|
27 |
.catch((errors) => { |
28 |
console.log('oops...') |
29 |
})
|
30 |
})
|
Di router pertama, anda menampilkan halaman /addBooks. Router ini dipanggil ketika permintaan dibuat pada lokasi /add. Sejak buku-buku ditambahkan seharusnya memiliki genre, anda ingin menampilkan genre yang sudah disimpan ke database.
1 |
const genres = Genre.find({}).exec() |
2 |
.then((genres) => { |
Kode di atas mencari semua genre dalam database anda dan mengembalikan mereka pada variabel genre. Dengan ini, anda akan dapat melakukan perulangan melaui genre-genre dan menampilkannya sebagai sebuah checkbox.
Router kedua menangani pengajuan form. Pertama, anda cek isi permintaannya untuk memastikan bahwa beberapa field tidak kosong. Disinilah middleware express-validator yang anda atur dalam app.js menjadi berguna. Jika ada kesalahan, halaman di-render ulang. Jika tidak ada, misalnya Buku baru disimpan dan pengguna akan diarahkan ke halaman /books.
Mari kita lanjutkan dan buat view untuk ini.
Buat sebuah file view baru bernama addBooks.pug. Perhatikan bahwa nama view sesuai dengan parameter pertama yang diberikan kepada res.render. Hal ini karena anda me-render sebuah template. Selama pengalihan, anda cukup melalui lokasi yang anda ingin arahkan ulang, seperti yang anda lakukan dengan res.redirect('/books').
Setelah menetapkan itu, berikut seperti apa seharusnya view akan terlihat.
1 |
#views/addBooks.pug |
2 |
|
3 |
extends layout |
4 |
|
5 |
block content |
6 |
.row |
7 |
.col-md-12 |
8 |
h1 Add Book |
9 |
form(method="POST", action="/books/add") |
10 |
.form-group |
11 |
label.col-lg-2.control-label Name |
12 |
.col-lg-10 |
13 |
input.form-control(type="text", name='name') |
14 |
.form-group |
15 |
label.col-lg-2.control-label Author |
16 |
.col-lg-10 |
17 |
input.form-control(type="text", name='author') |
18 |
.form-group |
19 |
label.col-lg-2.control-label Book Description |
20 |
.col-lg-10 |
21 |
textarea#textArea.form-control(rows='3', name='description') |
22 |
.form-group |
23 |
label.col-lg-2.control-label Genre |
24 |
.col-lg-10 |
25 |
for genre in genres |
26 |
.checkbox |
27 |
input.checkbox(type='checkbox', name='genre', id=genre._id, value=genre._id, checked=genre.checked) |
28 |
label(for=genre._id) #{genre.name}
|
29 |
.form-group |
30 |
.col-lg-10.col-lg-offset-2 |
31 |
input.button.btn.btn-primary(type='submit', value='Submit') |
32 |
|
33 |
if errors |
34 |
ul |
35 |
for error in errors |
36 |
li!= error.msg |
Hal yang penting untuk dicatat di sini adalah aksi form dan metode. Ketika tombol kirim diklik, anda membuat permintaan POST ke /books/add. Satu hal lagi—sekali lagi anda melakuan perulangan melalui koleksi genre yang dikembalikan dan menampilkannya masing-masing.
Route Tampil Buku dan View
Mari kita turunkan route untuk menangani permintaan yang dilakukan untuk setiap halaman buku. Sementara anda di sana, sangat penting untuk mengekspor modul anda juga.
1 |
#routes/books.js |
2 |
|
3 |
router.get('/show/:id', (req, res, next) => { |
4 |
const book = Book.findById({ _id: req.params.id }) |
5 |
.populate({ |
6 |
path: 'genre', |
7 |
model: 'Genre', |
8 |
populate: { |
9 |
path: 'genre', |
10 |
model: 'Book' |
11 |
}
|
12 |
})
|
13 |
.exec() |
14 |
.then((book) => { |
15 |
res.render('book', { book }) |
16 |
})
|
17 |
.catch((err) => { |
18 |
throw err |
19 |
})
|
20 |
})
|
21 |
|
22 |
module.exports = router; |
Tidak ada keajaiban yang terjadi sini
Pertama, permintaan yang dilakukan ke router ini harus memiliki id: yaitu id dari buku. Id ini diperoleh dari parameter permintaan menggunakan req.params.id. Ini digunakan untuk mengidentifikasi buku tertentu yang harus diperoleh dari database, sebagai id yang unik. Ketika buku ditemukan, nilai buku genre diisi dengan semua genre yang telah disimpan untuk contoh buku ini. Jika semua berjalan lancar, view buku akan di-render, jika tidak sebuah error akan muncul.
Mari kita buat view untuk sebuah buku. Berikut adalah bagaimana hal ini akan terlihat.
1 |
block content |
2 |
.well.well-lg |
3 |
h1 #[strong Name:] #{book.name}
|
4 |
ul |
5 |
li #[strong Description:] #{book.description}
|
6 |
li #[strong Author]: #{book.author}
|
7 |
li #[strong Genre:] |
8 |
each genre in book.genre |
9 |
#{genre.name}
|
10 |
|, |
Anda dapat memuliai node server anda dengan menjalankan:
1 |
DEBUG=tutsplus-library:* npm start |
Kesimpulan
Sekarang Anda tahu bagaimana membangun sebuah aplikasi web standar di Node.js, bukan hanya sebuah aplikasi agenda sederhana. Anda mampu untuk menangani pengiriman form, mereferensi dua model, dan mengatur beberapa middleware.
Anda dapat melangkah lebih jauh dengan mengembangkan aplikasinya—coba tambahkan kemampuan untuk menghapus sebuah buku. Pertama tambahkan tombol ke halaman tampil, dan kemudian pergi ke file route dan tambahkan router untuk ini. Perhatikan bahwa ini akan menjadi permintaan POST.
Anda juga bisa memikirkan lebih banyak fitur untuk menambahkannya pada aplikasi. Saya harap anda menikmatinya.



