Construirea API-ului RESTful

Un API RESTful reprezint─â o metod─â de a expune date ╚Öi de a realiza opera╚Ťii pe acele date prin intermediul protocolului HTTP.

├Än acest exerci╚Ťiu construim un server web cu ajutorul NodeJS ╚Öi al framework-ului ExpressJS. Datele sunt stocate ├«ntr-o baz─â de date rela╚Ťional─â MySQL. Accesul la date se face prin Sequelize.

1. Cum ini╚Ťializez o aplica╚Ťie NodeJS?

Pentru a ini╚Ťializa o aplica╚Ťie NodeJS execut urm─âtoarea comand─â din terminal si completez detaliile pentru proiect

npm init

Urm─âre╚Öte instruc╚Ťiunile de pe ecran ╚Öi completez urm─âtoarele detalii:

  • Name - numele proiectului

  • Version - versiunea proiectului (default) - trec peste apasand ENTER

  • Description - o descriere succint─â

  • Entry point - fi╚Öierul care porne╚Öte aplica╚Ťia: server.js

  • Test command - trec peste apasand ENTER

  • Git repository - trec peste apasand ENTER

  • Keywords - trec peste apasand ENTER

  • Author - trec peste apasand ENTER

  • License - trec peste apasand ENTER

Dup─â ce ai completat toate informa╚Ťiile pe ecran va aparea un mesaj de confirmare:

npm init

Confrim dac─â datele introduse sunt corecte. Dac─â am executat comanda cu succes voi ob╚Ťine un fi╚Öier pagckage.json cu datele introduse.

  • TODO: verific─â existen╚Ťa fi╚Öierului package.json

  • TODO: creaz─â un fi╚Öier server.js

2. Cum construiesc un server HTTP folosind ExpressJS?

ExpressJS este un framework minimalist pentru dezvoltarea de aplica╚Ťii web, mobile sau servicii web REST (API).

Pentru desc─ârca modulul ╚Öi a include express ├«n lista de dependin╚Ťe execut─â comanda

npm install express --save

Editează fișierul server.js și adaugă modulul express

const express = require('express')

Pentru a folosi Express definește o constată app astfel

const app = express()

Pentru a servi fișiere statice precum imagini, documente html, css sau javascript folosește express.static

app.use('/', express.static('frontend'))

Primul parametru reprezintă calea din adresa url. Al doilea parametru este un apel al metodei static care primește calea către directorul în care sunt stocate fișierele statice.

În final voi specifica portul pe care serverul va asculta cererile HTTP.

app.listen(8080)

Pentru a rula programul executnode server.js

  • TODO: creaz─â un director denumit frontend

  • TODO: adaug─â ├«n directorul creat un fi╚Öier index.html

  • TODO: deschide ├«n browser aplica╚Ťia acces├ónd adresa URL (http://localhost:8080)

3. Cum instalez MySQL și cum creez baza de date?

Detaliile pentru a configura MySQL în Cloud9 sunt dispoibile aici.

În continuare adaugă baza de date cu numele profile executand în consola mysql comanda:

create database profile;
  • TODO: verific─â dac─â baza de date a fost creat─â cu succes execut├ónd show databases;

  • TODO: p─âr─âse╚Öte consola mysql execut├ónd comanda exit

4. Cum m─â conectez la baza de date din NodeJS folosind Sequelize?

Sequelize este o bibliotec─â orientat─â obiect de tip ORM (object-relational mapping). Permite realizarea unei reperezent─âri a tabelelor din baza de date prin modele ╚Öi rela╚Ťii ├«ntre modele. Un model este un o clas─â permite opera╚Ťiile standard pe baza de date (Create, Read, Update, Delete). Documenta╚Ťia oficial─â este accesibil─â aici: http://docs.sequelizejs.com/ÔÇő

Pentru a folosi Sequelize în proiect sunt necesare pachetele sequelize și mysql2 pe care le vom instala prin npm

npm install --save sequelize
npm install --save mysql2

Urm─âtorul pas presupune includerea pachetului ├«n fi╚Öierul server.js ╚Öi instan╚Ťierea unui obiect sequelize

const Sequelize = require('sequelize')
ÔÇő
const sequelize = new Sequelize('profile', 'root', '', {
dialect: "mysql",
host: "localhost"
})

Fac distinc╚Ťie ├«ntre obiectul sequelize ╚Öi clasa Sequelize scris─â cu liter─â mare. ├Än contstructorul clasei primul parametru este numele bazei de date, al doilea prametru este utilizatorul ╚Öi al treilea este parola. Ultimul parametru este un obiect ce descrie date despre tipul de baz─â de date folosit ╚Öi adresa serverului.

Pentru a realiza conexiunea la baza de date utilizez metoda authenticate()

Metoda returneaz─â un obiect de tip Promise pentru care trebuie s─â specific func╚Ťiile pe care s─â le apeleze atunct c├ónd conexiunea se realizeaz─â cu succes, respectiv ├«nt├ómpin o eroare.

sequelize.authenticate().then(() => {
console.log("Connected to database")
}).catch((err) => {
console.log(err)
console.log("Unable to connect to database")
})
  • TODO: testeaz─â conexiunea rul├ónd server.js

  • TODO: verific─â ├«n conosl─â dac─â apare mesajul Conected to database

5. Cum definesc modele pentru tabele folosind Sequelize?

Un model este o reprezentare a unui tabel ├«n codul surs─â al aplica╚Ťiei. Sequelize permite definirea de modele folosind func╚Ťia define()

Primul parametrul al func╚Ťiei este numele tabelului. O conven╚Ťie presupus─â de lucru cu Sequelize este c─â numele tabelului va fi definit ├«n limba englez─â la plural. Al doilea parametru este un obiect care descrie structura tabelului prin perechi cheie:valoare, unde cheia este numele coloanei ╚Öi valoarea este tipul de date.

const Messages = sequelize.define('messages', {
subject: Sequelize.STRING,
name: Sequelize.STRING,
message: Sequelize.TEXT
})

Mai multe detalii despre definirea de modele - http://docs.sequelizejs.com/manual/tutorial/models-definition.htmlÔÇő

Lista cu tipurile de date suportate de Sequelize - http://docs.sequelizejs.com/manual/tutorial/models-definition.html#data-typesÔÇő

Pentru a modela o aplica╚Ťie este necesar s─â porne╚Öti de la domeniul pe care ├«l adreseaz─â, s─â identifici entit─â╚Ťi ╚Öi rela╚Ťii ├«ntre entit─â╚Ťi, s─â stabile╚Öti care sunt propriet─â╚Ťile lor ╚Öi s─â identifici tipurile de date corespunz─âtoare. Este o activitate ce se desf─â╚Öoar─â de obicei iterativ ╚Öi incremenental pe parcursul dezvolt─ârii aplica╚Ťiei. A╚Öa c─â Sequelize propune un mecanism automat de sincronizare a bazei de date care este descris ├«n pasul urm─âtor.

6. Cum creez tabelele în baza de date folosind mecanismul de sincronizare din Sequelize?

Sequelize permite sincronizarea automat─â a modelelor cu baza de date prin intermediul func╚Ťiei sync()

Ad─âdug├ónd parametrul {force: true} tabelele existente vor fi ╚Öterse ╚Öi vor fi create confrom defini╚Ťiei din model.

Pentru a defini tabelel în baza de date expun enpoint-ul GET /createdb

app.get('/createdb', (request, response) => {
sequelize.sync({force:true}).then(() => {
response.status(200).send('tables created')
}).catch((err) => {
console.log(err)
response.status(200).send('could not create tables')
})
})
  • TODO: acceseaz─â din browser endpoint-ul /createdb

  • TODO: testeaz─â dac─â tabelul a fost creat execut├ónd ├«n consola mysql comenzile use profile pentru a selecta baza de date ╚Öi show tables; pentru a afi╚Öa lista de tabele din baza de date

Metode HTTP

├Än continuare vom dezvolta metode HTTP pentru fiecare opera╚Ťie Create, Read, Update, Delete

metode http

7. Cum creez o nouă înregistrare într-un tabel folosind metoda POST?

Pentru a permite crearea de înregistrări expun o metodă de tip POST.

Fiecare endpoint din API-ul REST este definită de metoda HTTP și numele resursei la care se referă.

POST /messages

Clientul va trimite datele prin cererea HTTP în format json sau urlencoded. Pentru a interpreta aceste date voi adăuga două bodyParser. Apoi definesc endpoint-ul apelând functia app.post.

app.use(express.json())
app.use(express.urlencoded())
ÔÇő
//definire endpoint POST /messages
app.post('/messages', (request, response) => {
Messages.create(request.body).then((result) => {
response.status(201).json(result)
}).catch((err) => {
response.status(500).send("resource not created")
})
})

Con╚Ťinutul trimis ├«n body va fi accesibil pe proprietatea request.body ce va fi pasat ca parametru pentru modelul Sequelize ├«n metoda create().

Metoda create Sequelize va genera automat instruc╚Ťiunea INSERT INTO messages (`subject`, `name`, `message`) VALUES ('test','test','test').

Dac─â comanda va fi executat─â cu succes rezultatul va fi returnat prin functia callback definit─â pe metoda then(callback)

Dac─â aplica╚Ťia va ├«nt├ómpina o eroare la scriere ├«n baza de date va apela functia callback definit─â pe metoda catch(callback)

Pentru a testa enpoint-ul creat folosim Postman.

postman post method
  1. Selecteaz─â metoda POST

  2. Adaug─â adresa URL a resursei

  3. În tabul body alege optiunea raw și adaugă un obiect json care descrie resursa creată

  4. Selecteaz─â tipul de continut application/json

  5. Trimite cererea HTTP apăsând butonul Send

8. Cum expun datele dintr-un tabel folosind metoda GET?

Pentru a lista datele dintr-un tabel vom expune două enpoint-uri. Primul care returnează toată lista de mesaje și al doilea care returnează un mesaj după un ID specific.

GET /messages
GET /messages/1

Pentru interogarea tabelului modelul sequelize expune metode precum findAll, findByPk, findOne. Functia va returna un obiect de tip Promise ce va fi executat imediat ce datele sunt primite de la serverul de baze de date. Mai multe detalii despre interogari aici: http://docs.sequelizejs.com/manual/tutorial/querying.htmlÔÇő

app.get('/messages', (request, response) => {
Messages.findAll().then((results) => {
response.status(200).json(results)
})
})
ÔÇő
app.get('/messages/:id', (request, response) => {
Messages.findByPk(request.params.id).then((result) => {
if(result) {
response.status(200).json(result)
} else {
response.status(404).send('resource not found')
}
}).catch((err) => {
console.log(err)
response.status(500).send('database error')
})
})
  • TODO: testeaz─â endpoint-urile create folosind Postman

9. Cum actualizez o înregistrare folosind metoda PUT?

Actualizarea unei resurse se realizeaz─â prin intermediul metodei PUT

PUT /messages/1

├Än primul pas se interogheaz─â baza de date. Dac─â resursa nu exist─â serverul va returna statusul 404 ╚Öi mesajul ÔÇ×not foundÔÇ×.

Dacă resursa a fost găsită o actualizez apelând metoda update() cu obiectul trimis în body-ul cererii HTTP.

app.put('/messages/:id', (request, response) => {
Messages.findByPk(request.params.id).then((message) => {
if(message) {
message.update(request.body).then((result) => {
response.status(201).json(result)
}).catch((err) => {
console.log(err)
response.status(500).send('database error')
})
} else {
response.status(404).send('resource not found')
}
}).catch((err) => {
console.log(err)
response.status(500).send('database error')
})
})

Pașii pentru a testa metoda PUT sunt aceiași ca pentru metoda POST.

postman put

10. Cum șterg o înregistrare folosind metoda DELETE?

Ultima metodă permite ștergerea unei resurse

DELETE /messages/1

Dacă resursa este găsită după ID, apelez metoda destroy,iar sequelize va transmite către baza de date instructiunea sql DELETE FROM `messages` WHERE id = 1 și va returna un obiect de tip Promise. În final serverul web va raspunde cu statusul 204 NO CONTENT semnalând că cererea a fost îndeplinită cu succes.

app.delete('/messages/:id', (request, response) => {
Messages.findByPk(request.params.id).then((message) => {
if(message) {
message.destroy().then((result) => {
response.status(204).send()
}).catch((err) => {
console.log(err)
response.status(500).send('database error')
})
} else {
response.status(404).send('resource not found')
}
}).catch((err) => {
console.log(err)
response.status(500).send('database error')
})
})
  • TODO: testeaz─â enpoint-ul ├«n Postman folosind metoda DELETE

11. S─â punem totul cap la cap

const express = require('express')
const Sequelize = require('sequelize')
ÔÇő
const sequelize = new Sequelize('profile', 'root', '', {
dialect: "mysql",
host: "localhost"
})
ÔÇő
sequelize.authenticate().then(() => {
console.log("Connected to database")
}).catch((err) => {
console.log(err)
console.log("Unable to connect to database")
})
ÔÇő
const Messages = sequelize.define('messages', {
subject: Sequelize.STRING,
name: Sequelize.STRING,
message: Sequelize.TEXT
})
ÔÇő
const app = express()
app.use('/', express.static('frontend'))
ÔÇő
app.use(express.json())
app.use(express.urlencoded())
ÔÇő
app.get('/createdb', (request, response) => {
sequelize.sync({force:true}).then(() => {
response.status(200).send('tables created')
}).catch((err) => {
console.log(err)
response.status(200).send('could not create tables')
})
})
ÔÇő
//definire endpoint POST /messages
app.post('/messages', (request, response) => {
Messages.create(request.body).then((result) => {
response.status(201).json(result)
}).catch((err) => {
response.status(500).send("resource not created")
})
})
ÔÇő
ÔÇő
app.get('/messages', (request, response) => {
Messages.findAll().then((results) => {
response.status(200).json(results)
})
})
ÔÇő
app.get('/messages/:id', (request, response) => {
Messages.findByPk(request.params.id).then((result) => {
if(result) {
response.status(200).json(result)
} else {
response.status(404).send('resource not found')
}
}).catch((err) => {
console.log(err)
response.status(500).send('database error')
})
})
ÔÇő
app.put('/messages/:id', (request, response) => {
Messages.findByPk(request.params.id).then((message) => {
if(message) {
message.update(request.body).then((result) => {
response.status(201).json(result)
}).catch((err) => {
console.log(err)
response.status(500).send('database error')
})
} else {
response.status(404).send('resource not found')
}
}).catch((err) => {
console.log(err)
response.status(500).send('database error')
})
})
ÔÇő
ÔÇő
app.delete('/messages/:id', (request, response) => {
Messages.findByPk(request.params.id).then((message) => {
if(message) {
message.destroy().then((result) => {
response.status(204).send()
}).catch((err) => {
console.log(err)
response.status(500).send('database error')
})
} else {
response.status(404).send('resource not found')
}
}).catch((err) => {
console.log(err)
response.status(500).send('database error')
})
})
ÔÇő
ÔÇő
app.listen(8080)

12. Simplific─âm codul folosind async/await

const express = require('express')
const Sequelize = require('sequelize')
ÔÇő
const sequelize = new Sequelize('profile', 'root', '', {
dialect: "mysql",
host: "localhost"
})
ÔÇő
sequelize.authenticate().then(() => {
console.log("Connected to database")
}).catch((err) => {
console.log(err)
console.log("Unable to connect to database")
})
ÔÇő
const Messages = sequelize.define('messages', {
subject: Sequelize.STRING,
name: Sequelize.STRING,
message: Sequelize.TEXT
})
ÔÇő
const app = express()
app.use('/', express.static('frontend'))
ÔÇő
app.use(express.json())
app.use(express.urlencoded())
ÔÇő
app.get('/createdb', async (request, response) => {
try {
await sequelize.sync({force:true})
response.status(200).send('tables created')
} catch(err) {
console.log(err)
response.status(200).send('could not create tables')
}
})
ÔÇő
//definire endpoint POST /messages
app.post('/messages', async (request, response) => {
try {
let result = await Messages.create(request.body)
response.status(201).json(result)
} catch(err) {
console.log(err)
response.status(500).send("resource not created")
}
})
ÔÇő
ÔÇő
app.get('/messages', async (request, response) => {
try {
let results = await Messages.findAll()
response.status(201).json(results)
} catch(err) {
console.log(err)
response.status(500).send("server error")
}
})
ÔÇő
app.get('/messages/:id', async (request, response) => {
try {
let result = await Messages.findByPk(request.params.id)
if(result) {
response.status(200).json(result)
} else {
response.status(404).send('resource not found')
}
} catch(err) {
console.log(err)
response.status(500).send('database error')
}
})
ÔÇő
app.put('/messages/:id', async (request, response) => {
try {
let message = await Messages.findByPk(request.params.id)
if(message) {
let result = await message.update(request.body)
response.status(201).json(result)
} else {
response.status(404).send('resource not found')
}
} catch(err) {
console.log(err)
response.status(500).send('database error')
}
})
ÔÇő
ÔÇő
app.delete('/messages/:id', async (request, response) => {
try {
let message = await Messages.findByPk(request.params.id)
if(message) {
await message.destroy()
response.status(204).send()
} else {
response.status(404).send('resource not found')
}
} catch(err) {
console.log(err)
response.status(500).send('database error')
}
})
ÔÇő
ÔÇő
app.listen(8080)

Next steps...

Dacă ai reușit să parcurgi tutorialul până aici, în primul rând felicitări pentru efort!

Iată câteva resurse care te vor ajuta să aprofundezi dezvoltarea de servicii web REST: