Express.js менен REST API сервер кантип жасоо: нөлдөн production'го чейин (2015)
Кыскача: npm init, npm install express mysql2 jsonwebtoken bcryptjs, Express роутерлерин жаса, JWT middleware кош. Node.js 4 LTS (2015-жылдагы туруктуу версия) PHP'га салыштырмалуу: 3x тезирек JSON API үчүн, бирок асинхрондуу код стилин үйрөнүү керек.
Долбоор структурасы
myapi/
├── package.json
├── server.js ← Башкы файл
├── config/
│ └── database.js ← MySQL байланышы
├── middleware/
│ └── auth.js ← JWT текшерүү
├── routes/
│ ├── auth.js ← /api/auth/...
│ ├── orders.js ← /api/orders/...
│ └── products.js ← /api/products/...
└── models/
├── User.js
└── Order.js
Орнотуу
mkdir myapi && cd myapi
npm init -y
# Негизги зависимостуктар
npm install express mysql2 jsonwebtoken bcryptjs cors body-parser
# Dev зависимостуктары
npm install --save-dev nodemon
server.js
// server.js
var express = require('express');
var bodyParser = require('body-parser');
var cors = require('cors');
var authRoutes = require('./routes/auth');
var orderRoutes = require('./routes/orders');
var productRoutes = require('./routes/products');
var app = express();
var PORT = process.env.PORT || 3000;
// Middleware
app.use(cors());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
// Логировоние ар бир запрос
app.use(function(req, res, next) {
console.log(new Date().toISOString() + ' ' + req.method + ' ' + req.path);
next();
});
// Роутлер
app.use('/api/auth', authRoutes);
app.use('/api/orders', orderRoutes);
app.use('/api/products', productRoutes);
// 404 обработчик
app.use(function(req, res) {
res.status(404).json({ error: 'Not found' });
});
// Ката обработчику
app.use(function(err, req, res, next) {
console.error(err.stack);
res.status(500).json({ error: 'Internal server error' });
});
app.listen(PORT, function() {
console.log('Server running on port ' + PORT);
});
module.exports = app;
config/database.js
// config/database.js
var mysql = require('mysql2');
var pool = mysql.createPool({
host: process.env.DB_HOST || 'localhost',
user: process.env.DB_USER || 'root',
password: process.env.DB_PASS || '',
database: process.env.DB_NAME || 'deliveryapp',
charset: 'utf8mb4', // Кыргыз/орус тексти үчүн
connectionLimit: 10, // Connection pool'дун максимум байланышы
timezone: '+06:00', // Бишкек UTC+6
});
// Promise-based wrapper (Node 4'та async/await жок)
var db = {
query: function(sql, params) {
return new Promise(function(resolve, reject) {
pool.execute(sql, params || [], function(err, results) {
if (err) reject(err);
else resolve(results);
});
});
}
};
module.exports = db;
middleware/auth.js
// middleware/auth.js
var jwt = require('jsonwebtoken');
var JWT_SECRET = process.env.JWT_SECRET || 'change-this-in-production';
function authenticate(req, res, next) {
var authHeader = req.headers['authorization'];
if (!authHeader || authHeader.indexOf('Bearer ') !== 0) {
return res.status(401).json({ error: 'Token талап кылынат' });
}
var token = authHeader.substring(7);
jwt.verify(token, JWT_SECRET, function(err, decoded) {
if (err) {
if (err.name === 'TokenExpiredError') {
return res.status(401).json({ error: 'Token мөөнөтү өттү', code: 'TOKEN_EXPIRED' });
}
return res.status(401).json({ error: 'Жараксыз token' });
}
req.userId = decoded.userId;
req.userRole = decoded.role;
next();
});
}
module.exports = { authenticate };
routes/auth.js
// routes/auth.js
var express = require('express');
var bcrypt = require('bcryptjs');
var jwt = require('jsonwebtoken');
var db = require('../config/database');
var router = express.Router();
var JWT_SECRET = process.env.JWT_SECRET;
// POST /api/auth/login
router.post('/login', function(req, res) {
var email = req.body.email;
var password = req.body.password;
if (!email || !password) {
return res.status(400).json({ error: 'Email жана сырсөз керек' });
}
db.query('SELECT * FROM users WHERE email = ? AND is_active = 1', [email])
.then(function(rows) {
if (rows.length === 0) {
return res.status(401).json({ error: 'Email же сырсөз туура эмес' });
}
var user = rows[0];
bcrypt.compare(password, user.password_hash, function(err, valid) {
if (!valid) {
return res.status(401).json({ error: 'Email же сырсөз туура эмес' });
}
var token = jwt.sign(
{ userId: user.id, role: user.role },
JWT_SECRET,
{ expiresIn: '7d' }
);
res.json({
token: token,
user: {
id: user.id,
name: user.name,
role: user.role,
}
});
});
})
.catch(function(err) {
console.error(err);
res.status(500).json({ error: 'Сервер катасы' });
});
});
// POST /api/auth/register
router.post('/register', function(req, res) {
var name = req.body.name;
var email = req.body.email;
var password = req.body.password;
var phone = req.body.phone;
if (!name || !email || !password) {
return res.status(400).json({ error: 'Бардык талааларды толтуруңуз' });
}
var hash = bcrypt.hashSync(password, 10);
db.query(
'INSERT INTO users (name, email, password_hash, phone, role) VALUES (?, ?, ?, ?, ?)',
[name, email, hash, phone || null, 'customer']
)
.then(function(result) {
var token = jwt.sign(
{ userId: result.insertId, role: 'customer' },
JWT_SECRET,
{ expiresIn: '7d' }
);
res.status(201).json({ token, userId: result.insertId });
})
.catch(function(err) {
if (err.code === 'ER_DUP_ENTRY') {
return res.status(409).json({ error: 'Бул email катталган' });
}
res.status(500).json({ error: 'Сервер катасы' });
});
});
module.exports = router;
routes/orders.js
// routes/orders.js
var express = require('express');
var db = require('../config/database');
var auth = require('../middleware/auth');
var router = express.Router();
// Бардык заказдар роуттары авторизация талап кылат
router.use(auth.authenticate);
// GET /api/orders — азыркы колдонуучунун заказдары
router.get('/', function(req, res) {
db.query(
'SELECT * FROM orders WHERE user_id = ? ORDER BY created_at DESC LIMIT 50',
[req.userId]
)
.then(function(orders) {
res.json({ orders: orders });
})
.catch(function(err) {
res.status(500).json({ error: 'Заказдарды алуу мүмкүн болбоду' });
});
});
// POST /api/orders — жаңы заказ
router.post('/', function(req, res) {
var items = req.body.items;
var address = req.body.address;
var phone = req.body.phone;
if (!items || !items.length || !address) {
return res.status(400).json({ error: 'Заказ маалыматы жетишсиз' });
}
// Жөнөкөй валидация
var total = items.reduce(function(sum, item) {
return sum + (item.price * item.qty);
}, 0);
db.query(
'INSERT INTO orders (user_id, total, address, phone, status) VALUES (?, ?, ?, ?, ?)',
[req.userId, total, address, phone, 'new']
)
.then(function(result) {
var orderId = result.insertId;
var promises = items.map(function(item) {
return db.query(
'INSERT INTO order_items (order_id, product_id, qty, price) VALUES (?, ?, ?, ?)',
[orderId, item.product_id, item.qty, item.price]
);
});
return Promise.all(promises).then(function() { return orderId; });
})
.then(function(orderId) {
res.status(201).json({ order_id: orderId, message: 'Заказ кабыл алынды' });
})
.catch(function(err) {
console.error(err);
res.status(500).json({ error: 'Заказды сактоо мүмкүн болбоду' });
});
});
module.exports = router;
Production'го иштетүү (PM2)
# PM2 орнотуу
npm install -g pm2
# Иштетүү
pm2 start server.js --name delivery-api
# Система жүргөндө автоматтык старт
pm2 startup
pm2 save
# Логдор
pm2 logs delivery-api
pm2 monit
PHP vs Node.js: биздин тажрыйба (2015)
| PHP + Laravel | Node.js + Express | |
|---|---|---|
| JSON API ылдамдыгы | 380 req/s | 1,200 req/s |
| Орнотуу татаалдыгы | Жеңил | Орточо |
| Команда тажрыйбасы | Жогору | Аз |
| Debug жеңилдиги | Жеңил | Татаал (async) |
JSON API үчүн Node.js 3x тезирек болду. Бирок командабыз PHP'да тажрыйбалуу болчу — таза ылдамдык üчün кайра үйрөнүүгө убакыт коротууга туура келди.