Биз жөнүндөБлогБайланыш
Backend2015-ж., 5-октябрь 5 мин 13

Express.js менен REST API сервер кантип жасоо: нөлдөн production'го чейин (2015)

AunimedaAunimeda
📋 Мазмуну

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 кайра үйрөнүүгө убакыт коротууга туура келди.

Ошондой эле окуңуз

Vue.js менен биринчи долбоор: Бишкектеги тажрыйба (2016)aunimeda
Frontend

Vue.js менен биринчи долбоор: Бишкектеги тажрыйба (2016)

Vue.js 2.0 2016-жылдын октябрь айында чыкты. React'ка салыштырмалуу жеңилирек үйрөнүлгөн. Биз Бишкектеги бир кардар үчүн онлайн-дүкөндүн себет бөлүмүн Vue.js менен жасадык. Components, reactivity, Vuex — реалдуу мисалдар менен.

MySQL суроо-талаптарын кантип тездетүү керек: EXPLAIN жана индекстер (2015)aunimeda
Маалымат базасы

MySQL суроо-талаптарын кантип тездетүү керек: EXPLAIN жана индекстер (2015)

Жай MySQL суроо-талаптары 2015-жылда да, 2025-жылда да бирдей диагностикаланат: EXPLAIN, slow query log, индекс жетишпейт. Бул макалада реалдуу мисал менен 8 секунддук суроо-талапты 40 мс'ка чейин тездеткен жолду карайбыз — кодду өзгөртпөстөн.

React Native менен биринчи мобилдик тиркеме жасоо: Бишкек тажрыйбасы (2015)aunimeda
Мобилдик иштеп чыгуу

React Native менен биринчи мобилдик тиркеме жасоо: Бишкек тажрыйбасы (2015)

React Native 2015-жылдын март айында Facebook тарабынан жарыяланды. Биз iOS жана Android үчүн бир эле код жазуу мүмкүнчүлүгүнө ишандык. Биринчи production тиркемебизди 8 апта ичинде жасадык. Реалдуу код, реалдуу проблемалар, реалдуу жыйынтыктар.

Бизнесиңизге IT иштеп чыгуу керекпи?

Веб-сайттарды, мобилдик тиркемелерди жана AI чечимдерин иштеп чыгабыз. Акысыз консультация.

Консультация алуу Бардык макалалар