Express는 Node.js 환경의 서버 프로그램을 만들 때 사용하기 위한 빠르고(Fast) 개방적인(unopinionated) 간결한(minimalist) 웹 프레임워크입니다.
$ npm install express --save
Web server는 웹 페이지를 response body에 담아서 보내주는 서버입니다.
API server는 요청을 처리하고 처리한 결과를 response body에 담아서 보내주는 서버입니다.
오늘날 많은 API server들은 작업 결과를 이러한 JSON 형식으로 response body에 담아서 보냅니다.
import express from "express";
const app = express();
app.get("/hello", (req, res) => {
res.send("<h1>Hello Express</h1>");
});
app.listen(3000, () => {
console.log("Server is listening...");
});
route handler
라우팅 작업을 하는 콜백을 의미합니다.
listen()
메서드의 두 번째 파라미터로 전달된 콜백은 서버가 외부의 리퀘스트를 들을 준비를 마치고 나면 자동으로 실행합니다.
localhost
컴퓨터의 자기 자신을 가리키는 루프백(loopback) 주소인 127.0.0.1의 호스트명입니다.
import express from "express";
import members from "./members.js";
const app = express();
app.get("/api/members", (req, res) => {
res.send(members);
});
app.get("/api/members/:id", (req, res) => {
const { id } = req.params;
const member = members.find((m) => m.id === Number(id));
if (member) {
res.send(member);
} else {
res.status(404).send({ message: "There is no such member" });
}
});
app.listen(3000, () => {
console.log("Server is listening...");
});
route parameter
웹 애플리케이션에서 동적인 URL을 처리하기 위한 기능입니다. 일반적으로 URL에 특정 값을 변수로 사용해야 할 때 사용됩니다. 위의 예시에서 :id
같이 변수를 나타내는 토큰인 콜론(:
)으로 시작합니다.
Route parameter에 접근하려면 request의 params 객체에 접근하면 됩니다.
Route parameter로 받은 id 변수는 문자열이므로 숫자로 사용할 때에 Number 함수를 써야합니다
실패한 response의 message를 보여줄 때에도 문자열이 아닌 JSON 형태의 객체로 표현하는 것이 좋습니다. res.send()
메서드는 자동으로 전달된 값을 JSON 문자열로 변환하여 HTTP 응답 본문으로 설정합니다.
Resource(리소스)는 웹 애플리케이션에서 클라이언트가 요청하는 대상을 말합니다. 리소스는 일반적으로 URI(Uniform Resource Identifier)로 식별되며, 웹 애플리케이션에서는 이를 관리하기 위한 RESTful API를 사용합니다.
리소스는 웹 애플리케이션에서 제공하는 모든 것을 포함할 수 있습니다. 예를 들어, HTML 문서, 이미지, 동영상, JSON 데이터 등 다양한 형태의 데이터나 파일, 기타 웹 애플리케이션에서 사용되는 자원들을 모두 리소스로 간주할 수 있습니다.
RESTful API는 리소스에 대한 CRUD(Create, Read, Update, Delete) 연산을 HTTP 메서드(GET, POST, PUT, DELETE)와 연결하여 정의합니다. 이를 통해 클라이언트는 RESTful API를 통해 서버에서 제공하는 리소스를 생성, 조회, 수정, 삭제할 수 있습니다.
Query(쿼리)는 웹 애플리케이션에서 클라이언트가 서버에게 전달하는 매개변수(Parameter)를 말합니다. Query는 URI(Uniform Resource Identifier)에 포함될 수 있으며, 일반적으로 URL의 끝에 ?를 사용하여 표시하고, key=value 형태로 표현합니다.
app.get("/api/members", (req, res) => {
const { team } = req.query;
if (team) {
const teamMembers = members.filter((m) => m.team === team);
res.send(teamMembers);
} else {
res.send(members);
}
});
req.query
객체는 query string을 파싱하여 { team: engineering, ... }
와 같은 형태로 저장합니다.
express()로 생성한 객체의 get()
메서드는 특정 경로로 오는 GET 메서드를 가진 리퀘스트를 처리하는 라우트 핸들러를 등록합니다. POST 메서드처럼 다른 리퀘스트를 보내도 get()
메서드의 라우트 핸들러들은 실행되지 않습니다.
POST 리퀘스트의 경우에는 새로 추가하려는 리소스의 내용이 해당 request body에 담겨있어서 서버에서 별도로 처리해줘야 합니다.
app.post("/api/members", (req, res) => {
console.log(req.body);
});
일반 브라우저에서는 리소스를 request body에 담을 수 없기 때문에 여기서는 VScode의 rest client
라는 플러그인을 사용했습니다.
.http
확장자를 갖는 파일을 생성하면 VScode에서 서버로 바로 request를 보낼 수 있습니다.
GET http://localhost:3000/api/members
###
POST http://localhost:3000/api/members
Content-Type: application/json
{
"id": 11,
"name": "Zake",
"team": "Engineering",
"position": "Android Developer",
"emailAddress": "zake@google.com",
"phoneNumber": "010-xxxx-xxxx",
"admissionDate": "2023/05/06",
"birthday": "1995/09/27",
"profileImage": "profile11.png"
}
app 객체에 Middleware 함수를 실행하지 않았기 때문에 아직은 POST request를 보낼 수 없습니다.
미들웨어(Middleware)는 Express.js에서 요청(Request)과 응답(Response) 사이에 실행되는 함수입니다. 미들웨어 함수는 HTTP 요청과 응답을 조작하여 웹 애플리케이션을 확장하고 기능을 추가할 수 있습니다.
미들웨어는 app.use() 함수를 이용하여 등록하며, 다음과 같은 형태를 가집니다.
app.use(function(req, res, next) {
// 미들웨어 함수 코드
});
미들웨어 함수는 파라미터로 req(요청 객체), res(응답 객체), next(다음 미들웨어 함수)를 전달받습니다. 이 매개변수를 이용하여 다음과 같은 작업을 수행할 수 있습니다.
미들웨어는 여러 개를 연결하여 사용할 수 있습니다. 이 경우, 다음 미들웨어 함수를 호출하기 위해서는 next() 함수를 호출해야 합니다.
app.use(express.json());
app.post("/api/members", (req, res) => {
const newMember = req.body;
members.push(newMember);
res.send(newMember);
});
express.json()
Express.js에서 제공하는 내장 미들웨어 함수 중 하나로, JSON 형식의 요청(Request) 본문을 파싱하여 req.body 객체에 저장하는 역할을 수행합니다.
express는 그 코드가 GitHub라고 하는 곳에 공개된 오픈 소스 프로젝트입니다. expressjs/express: Fast, unopinionated, minimalist web framework for node.
이 중에서 express
를 import할 때 참조되는 파일인 index.js
를 보면
index.js
파일은 lib라는 디렉토리 안에 있는 express라는 파일에서 import하는 것들을 그대로 export합니다.
lib/express.js
파일을 열어서 그 상단 부분을 보면
createApplication
이라는 함수를 공개하고, 함수가 실행될 때 app
이라는 함수를 리턴합니다.
import express from "express";
const app = express();
여기서 app
객체는 app
이라는 express 내장 함수였던 것입니다. 스크롤을 조금 더 내려보면
이런 식으로 createApplication
함수에 json
이라는 프로퍼티를 설정하고 있습니다.
json
프로퍼티에 설정된 bodyParser
는 express 패키지가 의존하고 있는 다른 패키지에 존재하는 객체입니다. express 패키지의 package.json 파일을 살펴보면
이 패키지가 의존하고 있는 패키지 중에 body-parser라는 패키지가 있습니다.
body-parser - npm
body-parser 패키지는 리퀘스트가 라우트 핸들러에 의해 처리되기 전에 리퀘스트의 바디에 관한 전처리(preprocessing) 기능을 갖고 있는 패키지입니다. body-parser에는 JSON 형식의 데이터를 파싱하는 기능도 있음을 알 수 있습니다.body-parser 패키지 레포지토리를 보면
일단 exports
가 가리키는 객체가 공개되고 있고, 그 아래 보이는, Object.defineProperty
로 시작되는 코드는 exports
객체에 json
이라는 프로퍼티를 설정하는 코드입니다. 지금 defineProperty
메서드의 세 번째 아규먼트로 들어온 객체는 json이라는 프로퍼티 자체가 가질 속성들으로, 여기서 get이라는 부분은 나중에 이 프로퍼티에 접근할 때 실행될 getter 함수를 의미합니다.
₩express.json₩은 bodyParser.json
을 의미하고 그것은 지금 보이는 createParserGetter('json')
의 리턴값을 가리킵니다.
createParserGetter
함수는 get
이라는 함수를 리턴합니다. 이 get
이라는 함수는 다시 loadParser
함수의 리턴 결과를 리턴합니다. loadParser
함수도 그 밑에 있습니다. loadParser에
문자열 'json'을 넣고 실행하면 또다른 파일에서 가져온 parser
를 parsers
객체에 설정하고 parser
를 리턴합니다. ./lib/types/json
파일은 json
이라는 함수를 리턴하고 있습니다. 그리고 json
함수는 결국 jsonParser
라는 함수를 리턴합니다.
결론적으로 express.json()
은 지금 보이는 jsonParser
라고 하는 함수를 리턴하는 부분이었던 겁니다.
app.use(express.json());
위 코드는
app.use(jsonParser);
위 코드와 같습니다. 이 jsonParser
가 바로 미들웨어입니다.
body-parser/json.js at master · expressjs/body-parser · GitHub 참조
// package.json
{
// ...
"scripts": {
"start": "node app.js",
"dev": "nodemon app.js"
},
// ...
}
package.json
의 "scripts"를 이렇게 설정하면 콘솔에서 npm start
로 "node app.js"를 실행할 수 있고, npm run dev
로 "nodemon app.js"를 실행할 수 있습니다.
npm install nodemon --save-dev
--save-dev
옵션은 해당 모듈을 devDependencies
에 추가하라는 옵션으로, 이 옵션을 함께 사용하면 설치된 모듈은 프로젝트를 개발할 때만 사용되는 모듈로 등록됩니다. 이 옵션을 사용하지 않으면 default로 --save-prod
옵션이 적용됩니다.
npm install --production
를 실행해서 devDependencies 필드에 있던 패키지들은 제외하고, dependencies 필드에 있던 패키지들만 node_modules 디렉토리에 재설치할 수 있습니다.
// app.js
import express from "express";
import members from "./members.js";
const app = express();
app.use(express.json());
// ...
app.put("/api/members/:id", (req, res) => {
const { id } = req.params;
const newInfo = req.body;
const member = members.find((m) => m.id === Number(id));
if (member) {
Object.keys(newInfo).forEach((prop) => {
member[prop] = newInfo[prop];
});
res.send(member);
} else {
res.status(404).send({ message: "There is no member with the id!" });
}
});
app.listen(3000, () => {
console.log("Server is listening...");
});
Object.keys()
메소드를 사용하여 key 값이 포함된 newIfno
객체의 모든 배열을 가져옵니다.
Object.keys()
는 자바스크립트에서 사용되는 내장 함수 중 하나로, 객체의 속성 이름이 포함된 배열을 반환합니다.
const newInfo = {
"id": 11,
"name": "William",
"team": "Engineering",
"position": "Android Developer",
"emailAddress": "zake@google.com",
"phoneNumber": "010-xxxx-xxxx",
"admissionDate": "2021/06/12",
"birthday": "1995/09/27",
"profileImage": "profile11.png"
}
console.log(Object.keys(newInfo)); // !
Object.entries()
는 자바스크립트에서 사용되는 내장 함수 중 하나로, 객체의 속성과 값이 쌍으로 묶인 배열을 반환합니다. 이 배열은 2차원 배열 형태로, 각 요소는 [key, value]
의 형태로 구성됩니다.
const newInfo = {
id: 11,
name: 'William',
team: 'Engineering',
position: 'Android Developer',
emailAddress: 'zake@google.com',
phoneNumber: '010-xxxx-xxxx',
admissionDate: '2021/06/12',
birthday: '1995/09/27',
profileImage: 'profile11.png',
};
console.log(Object.entries(newInfo)); // !
프로퍼티의 이름 뿐만 아니라 프로퍼티의 값도 바로 동시에 가져오는 것이 가능합니다.
const newInfo = {
id: 11,
name: 'William',
team: 'Engineering',
position: 'Android Developer',
emailAddress: 'zake@google.com',
phoneNumber: '010-xxxx-xxxx',
admissionDate: '2021/06/12',
birthday: '1995/09/27',
profileImage: 'profile11.png',
};
Object.entries(newInfo).forEach((pair) => {
console.log(`Key: ${pair[0]} => Value: ${pair[1]}`);
});
const newInfo = {
id: 11,
name: 'William',
team: 'Engineering',
position: 'Android Developer',
emailAddress: 'zake@google.com',
phoneNumber: '010-xxxx-xxxx',
admissionDate: '2021/06/12',
birthday: '1995/09/27',
profileImage: 'profile11.png',
};
for (const property in newInfo) {
console.log(`Key: ${property} => Value: ${newInfo[property]}`);
}
app.delete("/api/members/:id", (req, res) => {
const { id } = req.params;
const membersCount = members.length;
const index = members.findIndex((member) => member.id === Number(id));
if (index !== -1) {
members.splice(index, 1);
}
if (members.length < membersCount) {
res.send({ message: "Deleted" });
} else {
res.status(404).send({ message: "There is no momeber with the id!" });
}
});
app.listen(3000, () => {
console.log("Server is listening...");
});
members
가 import한 constant라서 members
변수에 재할당하지 않고 splice
메서드로 삭제했다.
Reference