๐Ÿ” Archive/Node.js

Express.js ํŠœํ† ๋ฆฌ์–ผ (GET, POST, PUT, DELETE)

YoungRock 2020. 3. 4. 03:42

Express?

์›น ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋งŒ๋“œ๋Š” ๋น ๋ฅด๊ณ  ๊ฐ€๋ฒผ์šด ํ”„๋ ˆ์ž„์›Œํฌ

๋”๋ณด๊ธฐ

Client  : Front-end part

Server : Back-end part (๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๊ฑฐ๋‚˜ ์ €์žฅ)

RESTful Services

REST: Representational State Transfer


CRUD Operations

HTTP Methods

  • GET: getting data
  • POST: creating data
  • PUT: updating data
  • DELETE: deleting data

API example

GET /api/customers
GET /api/customers/1
PUT /api/customers/1
DELETE /api/customers/1
POST /api/customers
  • RESTful convention(ํ˜‘์•ฝ) (customer -> ๊ฐ„๋‹จํ•˜๊ณ  ์˜๋ฏธ์žˆ๋Š” ์ฃผ์†Œ ์‚ฌ์šฉ)
  • Create, update ํ•˜๋Š”๋ฐ ํ‘œ์ค€ http ๋ฉ”์†Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•จ

Introducing Express

  • ์ €๋ฒˆ๊ฐ•์˜ ์ฝ”๋“œ(Before)
// app.js
const http = require('http');

const server = http.createServer(function(req, res){
    if(req.url === '/'){
        res.write('Hello World');
        res.end();
    }
    
    if(req.url === '/api/courses'){
        res.write(JSON.stringify([1,2,3]));  // convert this array->JSON syntex
        res.end();
    }
    
}); //create web server

server.listen(3000);

console.log('Listening on port 3000...');

ํ•˜์ง€๋งŒ, ์ด๋ ‡๊ฒŒํ•˜๋ฉด ์—ฌ๋Ÿฌ ๊ฐœ์˜ ๋ผ์šฐํ„ฐ๋ฅผ ๊ฐ€์กŒ์„ ๋•Œ if๋ฌธ์ด ๋งŽ์•„์ง€๋ฉด์„œ ์ฝ”๋“œ๊ฐ€ ๋ณต์žกํ•ด์ง
-> express๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊น”๋”ํ•œ ๊ตฌ์กฐ๋กœ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค.


Your First Web Server

Handling GET Requests

  • ์†Œ์Šค์ฝ”๋“œ
// index.js
const express = require('express');
const app = express();

app.get('/', (req, res) => {
    res.send('Hello World');
}) //arg: url(path), callback

app.get('/api/courses', (req, res)=>{
    res.send([1, 2, 3]);
});

app.listen(3000, () => console.log('Listening on port 3000'));

* express documentation (http://expressjs.com/en/4x/api.html#req)

  • ์‹คํ–‰๊ฒฐ๊ณผ

cmd
ํฌ๋กฌ์—์„œ ์‹คํ–‰1
ํฌ๋กฌ์—์„œ ์‹คํ–‰2


+ Tip

Nodemon

  • ์ง€๊ธˆ๊นŒ์ง€๋Š” ์ฝ”๋“œ๋ฅผ ๋ณ€๊ฒฝํ• ๋•Œ๋งˆ๋‹ค ์„œ๋ฒ„๋ฅผ ์ข…๋ฃŒ(ctrl+c) ์‹œ์ผฐ๋‹ค๊ฐ€ ๋‹ค์‹œ ์‹คํ–‰ํ–ˆ๋‹ค.
  • nodemon์„ ์„ค์น˜ํ•˜๋ฉด ์„œ๋ฒ„๋ฅผ ๊ป๋‹ค ์ผœ์ง€ ์•Š์•„๋„ ์ €์ ˆ๋กœ ๋ณ€๋™๋œ ํŒŒ์ผ์ด ์ ์šฉ๋œ๋‹ค.

์„ค์น˜

npm i -g nodemon

global๋กœ nodemon์„ค์น˜

์‹คํ–‰

nodemon index.js

 node๋กœ ์‹คํ–‰ํ•˜๋Š” ๋Œ€์‹ ์— nodemon์œผ๋กœ ์‹คํ–‰


Environment Variables

  • Before์ฝ”๋“œ์—์„œ ๊ฐœ์„ ํ•  ์ : ํฌํŠธ ๋„˜๋ฒ„๊ฐ€ ํ•˜๋“œ์ฝ”๋”ฉ ๋˜์–ด ์žˆ๋‹ค.
  • ํฌํŠธ๋„˜๋ฒ„๋Š” ํ˜ธ์ŠคํŒ… ํ™˜๊ฒฝ์— ๋”ฐ๋ผ ๋™์ ์œผ๋กœ ํ• ๋‹น๋˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค
  • Environment Variables์„ ์ด์šฉํ•˜์—ฌ ์ด๋ฅผ ๊ฐœ์„ 
  • Port: ๋Œ๊ณ  ์žˆ๋Š” ํ”„๋กœ์„ธ์Šค์˜ ํ™˜๊ฒฝ

[์ฝ”๋“œ]

const port = process.env.PORT || 3000;
app.listen(port, () => console.log(`Listening on port ${port}`));

[์‹คํ–‰๊ฒฐ๊ณผ]

environment port๊ฐ€ ์—†์œผ๋ฏ€๋กœ ํฌํŠธ๊ฐ€ ๋””ํดํŠธ๋กœ ์„ค์ •๋œ 3000๋ฒˆ์ด๋‹ค

๊ทธ๋Ÿผ, environment port๋ฅผ ์„ค์ •ํ•œ ํ›„ ์‹คํ–‰ํ•ด ๋ณด์ž

mac์ธ๊ฒฝ์šฐ export๋กœ, window์ธ ๊ฒฝ์šฐ set์œผ๋กœ port ๋ฒˆํ˜ธ๋ฅผ ์„ค์ •ํ•œ๋‹ค


Route Parameters

[์ฝ”๋“œ]

app.get('/api/courses/:id', (req, res) => {
    res.send(req.params.id);
})

์—ฌ๊ธฐ์—์„œ :id๋Š” parameter์ด๋‹ค.

[์‹คํ–‰๊ฒฐ๊ณผ]

:id์— ํ•ด๋‹นํ•˜๋Š” ๋ถ€๋ถ„์— 1์ด๋ผ๋Š” ๊ฐ’์„ ์ฃผ์—ˆ์œผ๋ฏ€๋กœ 1์„ ์ถœ๋ ฅํ•œ๋‹ค.

  • ๋‘๊ฐœ์˜ ํŒŒ๋ผ๋ฏธํ„ฐ๋„ ๊ฐ€๋Šฅํ•˜๋‹ค.

[์ฝ”๋“œ]

app.get('/api/posts/:year/:month', (req, res) => {
    res.send(req.params);
})

ํŒŒ๋ผ๋ฏธํ„ฐ year์™€ month

[์‹คํ–‰๊ฒฐ๊ณผ]

Query string parameter

  • ๋ฐฑ์•ค๋“œ ์„œ๋น„์Šค์— ์ถ”๊ฐ€์ ์ธ ๋ฐ์ดํ„ฐ๋ฅผ ์ œ๊ณตํ•œ๋‹ค (optional)
  • ๋ผ์šฐํ„ฐ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ๋Š” ๊ธฐ๋ณธ์ ์ด๊ณ  ํ•„์š”ํ•œ ๊ฐ’์„ ์ œ๊ณต

URL: http://localhost:3000/api/post/2020/3?sortBy=name

[์ฝ”๋“œ]

app.get('/api/posts/:year/:month', (req, res) => {
    res.send(req.query);
})

[์‹คํ–‰๊ฒฐ๊ณผ]


GET url ์ถ”๊ฐ€ ๋ฐ ์˜ˆ์™ธ์ฒ˜๋ฆฌ(404)

 [์ฝ”๋“œ] : GET /api/courses/:id

// index.js
const express = require('express');
const app = express();

const courses = [
    { id: 1, name: 'course1'},
    { id: 2, name: 'course2'},
    { id: 3, name: 'course3'},
];

app.get('/', (req, res) => {
    res.send('Hello World');
})

app.get('/api/courses', (req, res)=>{
    res.send(courses);
});

app.get('/api/courses/:id', (req, res) => {
    const course = courses.find(c => c.id === parseInt(req.params.id))  
    if(!course) res.status(404).send('The course with the given ID was not found');
    res.send(course);
});
  • find(): javascript์—์„œ ์–ด๋–ค ๋ฐฐ์—ด์—์„œ๋“  ์“ธ ์ˆ˜ ์žˆ๋Š” ํ•จ์ˆ˜
  • object not found -> 404๋กœ ์ฒ˜๋ฆฌ
  • req.params.id: string์„ ๋ฆฌํ„ดํ•œ๋‹ค. -> parseInt(์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ํ•จ์ˆ˜) ์‚ฌ์šฉ

[์‹คํ–‰๊ฒฐ๊ณผ]

  • Course๊ฐ€ ๋ชฉ๋ก์— ์—†์œผ๋ฉด 404์˜ค๋ฅ˜, ๋ชฉ๋ก์— ์žˆ์œผ๋ฉด ๊ฐ•์ขŒ ์ •๋ณด ์ถœ๋ ฅ

์—†์œผ๋ฉด 404
์žˆ์œผ๋ฉด ๊ฐ•์ขŒ ์ •๋ณด ์ถœ๋ ฅ


Handling POST Requests

[์ฝ”๋“œ]

// index.js
const express = require('express');
const app = express();

app.use(express.json());    //json parsing ํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ

const courses = [
    { id: 1, name: 'course1'},
    { id: 2, name: 'course2'},
    { id: 3, name: 'course3'},
];

app.post('/api/courses/', (req, res) => {
    const course = {
        id: courses.length + 1,
        name: req.body.name
    };
    courses.push(course);
    res.send(course);
});
// ๋‚˜์ค‘์—๋Š” id๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์˜ํ•ด ์ž๋™์œผ๋กœ ํ• ๋‹น๋  ๊ฒƒ์ž„

const port = process.env.PORT || 3000;
app.listen(port, () => console.log(`Listening on port ${port}`));

body์— ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด๋‚ด์•ผํ•œ๋‹ค

์–ด๋–ป๊ฒŒ ํ…Œ์ŠคํŠธ?

-> Postman ์„ค์น˜ https://www.postman.com/downloads/

 

Postman | The Collaboration Platform for API Development

Simplify workflows and create better APIs – faster – with Postman, a collaboration platform for API development.

www.postman.com

[๊ฒฐ๊ณผ]

 jsonํ˜•์‹์˜ raw ๋ฐ์ดํ„ฐ๋ฅผ body๋กœ ๋„˜๊ฒจ์ค€๋‹ค


Input Validation

  • ๋ณด์•ˆ์„ ์œ„ํ•ด์„œ๋Š” ์ ˆ๋Œ€ client๊ฐ€ ๋ณด๋‚ด๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ๋ฏฟ์–ด์„œ๋Š” ์•ˆ๋œ๋‹ค.
  • ๋”ฐ๋ผ์„œ ์˜ˆ๋ฅผ๋“ค์–ด ์ด์™€ ๊ฐ™์ด (1) ์ด๋ฆ„์€ ์žˆ์–ด์•ผํ•˜๊ณ , (2) ๊ธธ์ด๋Š” ๋ฐ˜๋“œ์‹œ 3์ด์ƒ์ด์—ฌ์•ผ ํ•œ๋‹ค. ๋ผ๋Š” ์กฐ๊ฑด์„ ๊ฑธ์–ด ์œ ํšจ์„ฑ์„ ๊ฒ€์‚ฌํ•œ๋‹ค.
app.post('/api/courses/', (req, res) => {
    if(!req.body.name || req.body.name.length < 3){
        // 400 Bad Request
        res.status(400).send('Name is required and should be minimum 3 characters.');
        return;
    }

    const course = {
        id: courses.length + 1,
        name: req.body.name
    };
    courses.push(course);
    res.send(course);
});

ํ•˜์ง€๋งŒ ์‹ค์ œ๋กœ๋Š” input๋„ ๋” ๋งŽ์„ ๊ฒƒ์ด๊ณ  ๋” ๋ณต์žกํ•œ validation logic์ด ํ•„์š”ํ•˜๋‹ค.


Joi ์„ค์น˜

  • Joi๋Š” ์ด๋Ÿฐ validation ๊ฒ€์‚ฌ๋ฅผ ๊ฐ„ํŽธํ•˜๊ฒŒ ํ• ์ˆ˜์žˆ๋„๋ก ์ œ๊ณต๋˜๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋‹ค.
  • ์„ค์น˜(Termianl์—์„œ) : npm i joi
  • ์›ํ•˜๋Š” ๋ฒ„์ „ ์„ค์น˜ : npm i joi@13.1.0

[Joi๋ฅผ ์ด์šฉํ•œ input validation]

//๋งจ์œ—์ค„์—
const Joi = require('joi'); // ํด๋ž˜์Šค๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฏ€๋กœ ๋ณ€์ˆ˜๋ฅผ ๋Œ€๋ฌธ์ž๋กœ ํ•จ

///////////////////////

app.post('/api/courses/', (req, res) => {
    const schema = {
        name: Joi.string().min(3).required()
    };

    const result = Joi.validate(req.body, schema);
    console.log(result);

    if(!req.body.name || req.body.name.length < 3){
        // 400 Bad Request
        res.status(400).send('Name is required and should be minimum 3 characters.');
        return;
    }

    const course = {
        id: courses.length + 1,
        name: req.body.name
    };
    courses.push(course);
    res.send(course);
});

 

[๊ฒฐ๊ณผ]

์กฐ๊ฑด์— ๋งž๋Š” ์ด๋ฆ„์ผ ๊ฒฝ์šฐ (error: null)
์ด๋ฆ„์„ ์ œ๊ณตํ•˜์ง€ ์•Š์œผ๋ฉด error: name is required๋ผ๊ณ  ๋œธ


์ด๋ ‡๊ฒŒ ์ฝ”๋“œ๋ฅผ ๋ฐ”๊ฟ€ ์ˆ˜ ์žˆ๋‹ค.

const schema = {
        name: Joi.string().min(3).required()
    };

    const result = Joi.validate(req.body, schema);
    if(result.error){
        res.status(400).send(result.error);
        return;
    }

 

[ํฌ์ŠคํŠธ๋งจ ๊ฒฐ๊ณผ]

์—๋Ÿฌ๋ฉ”์„ธ์ง€๋งŒ ์ถœ๋ ฅํ•˜๋„๋ก ์ฝ”๋“œ ๋ณ€๊ฒฝ

if(result.error){
        res.status(400).send(result.error.details[0].message);
        return;
    }

 

[๊ฒฐ๊ณผ]

์ด๋ฆ„์ด ์—†๋Š” ๊ฒฝ์šฐ
์ด๋ฆ„์ด 3๊ธ€์ž ์ดํ•˜์ธ ๊ฒฝ์šฐ

  • Joi๋Š” input์„ validateํ•˜๊ณ  ํด๋ผ์ด์–ธํŠธ์— ์ ์ ˆํ•œ ์—๋Ÿฌ๋ฉ”์„ธ์ง€๋ฅผ returnํ•˜๋Š” ๊ฒƒ์„ ๋งค์šฐ ์‰ฝ๊ฒŒํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์คŒ

Handling PUT Requests

[PUT Logic]

app.put('/api/courses/:id', (req, res) => {
    // Look up the course
    // If not existing, return 404

    // Validate
    // If invalid, return 400 - Bad request

    // Update course
    // Return the updated course
});

 

[์†Œ์Šค์ฝ”๋“œ]

// PUT

app.put('/api/courses/:id', (req, res) => {
    const course = courses.find(c => c.id === parseInt(req.params.id))  
    if(!course) res.status(404).send('The course with the given ID was not found');

    const { error } = validateCourse(req.body); //result.error
    if(error){
        res.status(400).send(error.details[0].message);
        return;
    }

    course.name = req.body.name;
    res.send(course);
});

function validateCourse(course){
    const schema = {
        name: Joi.string().min(3).required()
    };
    
    return Joi.validate(course, schema);
}

 validate ๋ถ€๋ถ„์„ ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•˜๋„๋ก validateCourseํ•จ์ˆ˜๋กœ ๋งŒ๋“ฆ

[๊ฒฐ๊ณผ]

name์ด course1์—์„œ new course๋กœ ๋ณ€๊ฒฝ๋จ(upate๊ธฐ๋Šฅ)


Handling DELTE Requests

[DELETE Logic]

// DELETE

app.delete('/api/courses/:is', (req, res) => {
    // Look up the course
    // Not existing, return 404

    // Delete

    // Return the same course
});

 

[์ฝ”๋“œ]

// DELETE

app.delete('/api/courses/:id', (req, res) => {
    const course = courses.find(c => c.id === parseInt(req.params.id))  
    if(!course) return res.status(404).send('The course with the given ID was not found');

    const index = courses.indexOf(course);
    courses.splice(index, 1);

    res.send(course);
});

 

[๊ฒฐ๊ณผ]

get์œผ๋กœ ๋ชจ๋‘ ํ™•์ธํ•ด๋ณด๋ฉด 3๋ฒˆ์ด ์—†์–ด์ง


Fixing a Bug ๋ฐ ์ตœ์ข… ์ฝ”๋“œ

// index.js
const Joi = require('joi'); // ํด๋ž˜์Šค๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฏ€๋กœ ๋ณ€์ˆ˜๋ฅผ ๋Œ€๋ฌธ์ž๋กœ ํ•จ
const express = require('express');
const app = express();

app.use(express.json());    //json parsing ํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ?

const courses = [
    { id: 1, name: 'course1'},
    { id: 2, name: 'course2'},
    { id: 3, name: 'course3'},
];

// GET

app.get('/', (req, res) => {
    res.send('Hello World');
})

app.get('/api/courses', (req, res)=>{
    res.send(courses);
});

app.get('/api/courses/:id', (req, res) => {
    const course = courses.find(c => c.id === parseInt(req.params.id))  
    if(!course) return res.status(404).send('The course with the given ID was not found');
    res.send(course);
});

// POST

app.post('/api/courses/', (req, res) => {
    const { error } = validateCourse(req.body); //result.error
    if(error) return res.status(400).send(error.details[0].message);

    const course = {
        id: courses.length + 1,
        name: req.body.name
    };
    courses.push(course);
    res.send(course);
});

// PUT

app.put('/api/courses/:id', (req, res) => {
    const course = courses.find(c => c.id === parseInt(req.params.id))  
    if(!course) return res.status(404).send('The course with the given ID was not found');

    const { error } = validateCourse(req.body); //result.error
    if(error) return res.status(400).send(error.details[0].message);

    course.name = req.body.name;
    res.send(course);
});

function validateCourse(course){
    const schema = {
        name: Joi.string().min(3).required()
    };
    
    return Joi.validate(course, schema);
}

// DELETE

app.delete('/api/courses/:id', (req, res) => {
    const course = courses.find(c => c.id === parseInt(req.params.id))  
    if(!course) return res.status(404).send('The course with the given ID was not found');

    const index = courses.indexOf(course);
    courses.splice(index, 1);

    res.send(course);
});

const port = process.env.PORT || 3000;
app.listen(port, () => console.log(`Listening on port ${port}`));