/ translation

Michael Herman - PostgreSQL and NodeJS

Сегодня мы собираемся написать одностраничное CRUD Todo приложение
с использованием технологий, Node JS, Express, Angular JS, и PostgreSQL.

Настройка проекта

Для начала нужно установить генератор Express
глобально, если он не установлен:

npm install -g express-generator@4

Затем создаем новый проект и устанавливаем зависимости:

$ express node-postgres-todo
$ cd node-postgres-todo && npm install

Так же установим node-supervisor 
глобально, чтобы наблюдать за изменениями в исходном коде:

$ npm install supervisor -g

Изменяем команду start в секции scripts в файле  package.json:

"scripts": {
  "start": "supervisor ./bin/www"
},

Запускаем наше приложение:

$ npm start

Затем перейдите по адресу http://localhost:3000/,
и в браузере Вы должны увидеть текст “Welcome to Express”.

Настройка Postgres

Если нужно установить Postgres на Mac? Для этого есть Postgres.app

После запуска Postgres сервера на порту 5432, для легкого подключения к базе
нужно установить pg библиотеку

$ npm install pg --save

Теперь для создания таблиц создадим простой скрипт:

var pg = require('pg');
var connectionString = process.env.DATABASE_URL || 'postgres://localhost:5432/todo';

var client = new pg.Client(connectionString);
client.connect();
var query = client.query('CREATE TABLE items(id SERIAL PRIMARY KEY, text VARCHAR(40) not null, complete BOOLEAN)');
query.on('end', function() { client.end(); });

Сохраните его как database.js файл, в новой папке models, в корне проекта.

В этом файле мы создали экземпляр класса Client для взаимодействия с базой данных, а
затем установили связь с ним с помощью connect() метода. После чего мы запускаем SQL
запрос с помощью query() метода. Связь закрывается с помощью end()метода. Обязательно
ознакомьтесь с разделом документации 
для получения дополнительной информации.

Убедитесь, что у вас есть база данных под названием "TODO", а затем запускаем скрипт
для настройки таблицы и последующих полей:

$ node models/database.js

Проверьте создание таблицы / схемы в PSQL:

michaelherman=# \c todo
You are now connected to database "todo" as user "michaelherman".
todo=# \d+ items
                                                     Table "public.items"
  Column  |         Type          |                     Modifiers                      | Storage  | Stats target | Description
----------+-----------------------+----------------------------------------------------+----------+--------------+-------------
 id       | integer               | not null default nextval('items_id_seq'::regclass) | plain    |              |
 text     | character varying(40) | not null                                           | extended |              |
 complete | boolean               |                                                    | plain    |              |
Indexes:
    "items_pkey" PRIMARY KEY, btree (id)

Вместе с подключением к базе мы создали таблицу items. Теперь переходим к следующему шагу - настройка нашего CRUD приложения.

Маршрутизация на стороне сервера:

Следующим шагом мы просто добавим функциональные блоки в файл index.js, который находиться в папке routes

var express = require('express');
var router = express.Router();
var pg = require('pg');
var connectionString = require(path.join(__dirname, '../', '../', 'config'));

Сейчас мы добавим каждый функциональный блок

Function URL Action
CREATE /api/v1/todos Создаем новую задачу
READ /api/v1/todos Получаем список задач
UPDATE /api/v1/todos/:todo_id Обновляем текущюю задачу
DELETE /api/v1/todos/:todo_id Удаляем текущюю задачу

Создание записи

router.post('/api/v1/todos', function(req, res) {

    var results = [];

    // Grab data from http request
    var data = {text: req.body.text, complete: false};

    // Get a Postgres client from the connection pool
    pg.connect(connectionString, function(err, client, done) {
        // Handle connection errors
        if(err) {
          done();
          console.log(err);
          return res.status(500).json({ success: false, data: err});
        }

        // SQL Query > Insert Data
        client.query("INSERT INTO items(text, complete) values($1, $2)", [data.text, data.complete]);

        // SQL Query > Select Data
        var query = client.query("SELECT * FROM items ORDER BY id ASC");

        // Stream results back one row at a time
        query.on('row', function(row) {
            results.push(row);
        });

        // After all data is returned, close connection and return results
        query.on('end', function() {
            done();
            return res.json(results);
        });


    });
});

Для тестирования наберите в терминале:

$ curl --data "text=test&complete=false" http://127.0.0.1:3000/api/v1/todos

Затем убедитесь, что данные были правильно занесены в базу данных с помощью PSQL:

todo=# SELECT * FROM items ORDER BY id ASC;
 id | text  | complete
----+-------+----------
  1 | test  | f
(1 row)

Чтение записи

router.get('/api/v1/todos', function(req, res) {

    var results = [];

    // Get a Postgres client from the connection pool
    pg.connect(connectionString, function(err, client, done) {
        // Handle connection errors
        if(err) {
          done();
          console.log(err);
          return res.status(500).json({ success: false, data: err});
        }

        // SQL Query > Select Data
        var query = client.query("SELECT * FROM items ORDER BY id ASC;");

        // Stream results back one row at a time
        query.on('row', function(row) {
            results.push(row);
        });

        // After all data is returned, close connection and return results
        query.on('end', function() {
            done();
            return res.json(results);
        });

    });

});

Добавьте пару пунктов с помощью Curl, и проверьте в браузере по адресу
http://localhost:3000/api/v1/todos.
Вы должны получить примерно такой результат:

[
    {
        "id": 1,
        "text": "test",
        "complete": false
    },
    {
        "id": 2,
        "text": "test2",
        "complete": false
    },
    {
        "id": 3,
        "text": "test3",
        "complete": false
    }
]

Обновление записей

router.put('/api/v1/todos/:todo_id', function(req, res) {

    var results = [];

    // Grab data from the URL parameters
    var id = req.params.todo_id;

    // Grab data from http request
    var data = {text: req.body.text, complete: req.body.complete};

    // Get a Postgres client from the connection pool
    pg.connect(connectionString, function(err, client, done) {
        // Handle connection errors
        if(err) {
          done();
          console.log(err);
          return res.status(500).send(json({ success: false, data: err}));
        }

        // SQL Query > Update Data
        client.query("UPDATE items SET text=($1), complete=($2) WHERE id=($3)", [data.text, data.complete, id]);

        // SQL Query > Select Data
        var query = client.query("SELECT * FROM items ORDER BY id ASC");

        // Stream results back one row at a time
        query.on('row', function(row) {
            results.push(row);
        });

        // After all data is returned, close connection and return results
        query.on('end', function() {
            done();
            return res.json(results);
        });
    });

});

Тестирум с помощью Curl:

$ curl -X PUT --data "text=test&complete=true" http://127.0.0.1:3000/api/v1/todos/1

Обновите страницу http://localhost:3000/api/v1/todos в браузере, что бы убедиться что все у нас правильно работает.

[
    {
        "id": 1,
        "text": "test",
        "complete": true
    },
    {
        "id": 2,
        "text": "test2",
        "complete": false
    },
    {
        "id": 3,
        "text": "test3",
        "complete": false
    }
]

Удаление записи

router.delete('/api/v1/todos/:todo_id', function(req, res) {

    var results = [];

    // Grab data from the URL parameters
    var id = req.params.todo_id;


    // Get a Postgres client from the connection pool
    pg.connect(connectionString, function(err, client, done) {
        // Handle connection errors
        if(err) {
          done();
          console.log(err);
          return res.status(500).json({ success: false, data: err});
        }

        // SQL Query > Delete Data
        client.query("DELETE FROM items WHERE id=($1)", [id]);

        // SQL Query > Select Data
        var query = client.query("SELECT * FROM items ORDER BY id ASC");

        // Stream results back one row at a time
        query.on('row', function(row) {
            results.push(row);
        });

        // After all data is returned, close connection and return results
        query.on('end', function() {
            done();
            return res.json(results);
        });
    });

});

Финальный Curl запрос для проверки наших роутеров:

$ curl -X DELETE http://127.0.0.1:3000/api/v1/todos/3

Мы видим следующий результат:

[
    {
        "id": 1,
        "text": "test",
        "complete": true
    },
    {
        "id": 2,
        "text": "test2",
        "complete": false
    }
]

Рефакторинг нашего TODO приложения

Перед тем, как перейти к клиентской стороне нашего приложения, нужно добавить Angular JS,
имейте в виду, что наш код должен быть переработан, чтобы решить несколько проблем. 
Но это отличная возможность реорганизовать код по своему усмотрению. Удачи!

Angular JS на стороне клиента

Давайте начнем работу с Angular JS

Учтите что этот урок не является полноценным учебным пособием для изучения Angular JS.
Если вы новичек в Angular JS, то я предлагаю посмотреть мой учебник "Angular JS на
примере" - Building a Bitcoin Investment Calculator.

Создание модуля

Создайте файл с именем app.js в папке public/javascripts.
В нем мы будем создавать наши модули и контроллеры для Angular JS

angular.module('nodeTodo', [])

.controller('mainController', function($scope, $http) {

    $scope.formData = {};
    $scope.todoData = {};

    // Get all todos
    $http.get('/api/v1/todos')
        .success(function(data) {
            $scope.todoData = data;
            console.log(data);
        })
        .error(function(error) {
            console.log('Error: ' + error);
        });
});

Здесь мы определили наш модуль в нашем контроллере. А в контроллере мы
используем $http
сервис для создания AJAX запроса к '/api/v1/todos' и обновляем $scope.