음.. 프론트엔드만 할 줄 알았는데
다른 개발자들 상황을 잘 아는건 아니지만,
회사성향에 따라 그리고 규모에따라 결국 풀스텍을 하게되는 경우가 왕왕 있는듯 하다
뭐 나도 그렇다
첫 회사에선 일렉트론으로 데스크탑 애플리케이션 만들다보니 node.js도 어찌어찌 작성했었는데
db 안쓰는 프로젝트고 api 통신도 아닌것이 뭔가 애매해서 백엔드 코드는 대충대충 했던 기억이 난다.
근데 지금회사에선 좀 본격적으로 백엔드 개발도 해야돼서 복습겸 회고하려고 한다.
프론트엔드 역량에 더 집중하고 싶기도한데 지금 회사에 필요한 건 이것저것 할 줄 아는 개발자인듯 하고 이렇게 역량 늘리는 것도 나쁘지 않긴하다.
기왕하는거 즐깁시다.
내가 생각하는 백엔드
프론트에서 필요한게 있으면 서버로 요청하고 서버는 응답한다.
백엔드는 응답하는 로직을 담당하는 부분이라고 생각한다
간단한 비즈니스 로직부터 db 조작까지해주는 정도?

근데 비즈니스 로직도 프론트엔드에서 private 하게 다룰수 있고 (feat. class 문법)
Next.js 13부터 서버 컴포넌트에서 바로 db CRUD가 가능해져서 프론트와 백엔드 경계가 모호하다고 느끼고있긴하다.
그래도 기본은 중요한거니까 기초를 다져봅시다.
어떻게 공부할까 하다가 전에 사둔 [리액트를 다루는 기술] 책에 백엔드 프로그래밍 부분이 있어서 진도를 따라가고 있다.
책에 수록된 스택은 Koa(기존 Express 팀이 개발한 더욱 가벼운 프레임워크) + Javascript + mongoose 인데,
Express + Typescript + mongoose 로 공부하고 싶어서 해당 스택으로 진행했다.
환경설정
1) express 설치
$ npm init
$ npm install express --save
2) Typescript 관련 설치
npm i -D typescript // typescript 설치 (-D 는 개발모드로)
npm i ts-node // 아래 후술
npm i @types/node
npm i @types/express
<ts-node>
- TypeScript를 JavaScript로 변환하여 사전 컴파일 없이 Node.js에서 TypeScript를 직접 실행할 수 있도록 합니다(문서 발췌) 이거 없으면 ts 파일 하나당 컴파일 된js 파일 하나 만들어지고 디버깅도 컴파일된 js 파일에서 해야돼서 매우 피곤함
<types/node>
- 이 패키지에는 노드에 대한 유형 정의가 포함되어 있습니다. (문서 발췌)
없으면 ts 파일에서 Node.js의 내장 모듈(예: fs, http, path 등)을 사용할 때 Type 에러남.
<types/express>
- 이 패키지에는 express 에 대한 유형 정의가 포함되어 있습니다. (문서 발췌)
없으면 ts 파일에서 express 사용할 때 Type 에러남
3) tsconfig.json 파일을 만들어 준다
npx tsc --init
4) nodemon 설치
npm i -D nodemon // ts파일이 변환되었을때 알아서 서버를 재시작해주는 유틸 라이브러리
5) npm 명령어로 nodemon 실행
// package.json
"scripts": {
"start": "nodemon server.ts"
},
Express
express 는 백엔드를 쉽게 즐기게 해주는 프레임워크.
Http 메서드 및 미들웨어를 통해 API 작성에 도움을 준다
// 코드 몇줄 딸깍으로 서버를 시작할 수 있다
const express = require('express')
const app = express()
const port = 3000
app.get('/', (req, res) => {
res.send('Hello World!')
})
app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
})
2) 기본 라우팅
// metods를 구분해 하나의 route에 4개의 요청과 응답이 가능하다
// postman을 사용해 작성한 api를 test 해보자
app.get('/', function (req, res) {
res.send('Hello World!');
});
app.post('/', function (req, res) {
res.send('Got a POST request');
});
app.put('/', function (req, res) {
res.send('Got a PUT request');
});
app.delete('/', function (req, res) {
res.send('Got a DELETE request');
});
mongoDB
몽고DB는 크로스 플랫폼 도큐먼트 지향 데이터베이스 시스템이다. NoSQL 데이터베이스로 분류되는 몽고DB는 JSON과 같은 동적 스키마형 도큐먼트들을 선호함에 따라 전통적인 테이블 기반 관계형 데이터베이스 구조의 사용을 삼간다
(나무 위키 발췌)
음.. 그냥 처음 db 써보기 좋은 친구.
처음 용어도 좀 헷갈렸는데 몽고DB에서 Document는 JSON 객체라고 보면 될듯하다
const dummy = [
{name: "철수"},
{name: "영희"},
];
위 dummy 배열을 몽고DB에 넣는다고 하면
1번 Document에는 {name: "철수"} 가
2번 Document에는 {name: "영희"} 가 들어가는 느낌?
(Document마다 _id 를 고유값으로 가진다.)
SQL 진형의 DB랑 비교해보면 스키마가 유동적이라 매우 쓰기 편하다.
RDBMS와 DB 설계 차이를 비교해보자
SQL 진형에서 포스팅 / 댓글 기능을 구현한다고 하면
user / post / comment 테이블을 설계한다.

하지만 NoSQL 에서는 하나의 Document에 쑤셔넣는다.
{
_id: ObjectId,
title: String,
body: String,
user_id: String,
reg_dt: Date,
commments: [
{
_id: ObjectId,
text: String,
reg_dt: Date
},
],
}
백엔드 개발을 시작했을때 이 DB구조에서 애먹었다.
적당히 설계했다가 필요한 데이터를 찾기위해 비슷한 컬렉션을 추가하는 등의 일이 왕왕 발생했다
(뭐 지금도 크게 다르진 않다..)
mongoose
mongoDB 확장판 정도로 데이터 모델링, 유효성 검사, 미들웨어 등 다양한 기능을 통해 개발자의 생산성을 높여준다
스키마가 고정되어있지 않은 mongoDB를 그냥 쓰면 같은 컬렉션의 Document 마다 data 형식이 다르게 들어가 점점 관리하기 어려워진다. (미들웨어 같은 기능은 express와 유사해 같이 공부하면서 자동으로 복습도 되고 좋았다.)
1) mongoose 시작하기
import mongoose from "mongoose";
import "dotenv/config";
const { MONGO_URI } = process.env;
async function connectDB() {
if (!MONGO_URI) throw new Error("환경변수 설정 필수!");
try {
await mongoose
.connect(MONGO_URI)
.then(() => {
console.log("mongodb connect!");
})
.catch((err) => console.error(err));
} catch (err) {
console.error(err);
}
}
export { connectDB };
2) 스키마(schema)를 만들기
스키마는 Document에 들어가는 데이터가 어떤 형식으로 되어 있는지 정의하는 객체.
모델은 스키마를 사용해 만드는 인스턴스로, 데이터베이스에서 실제 작업을 처리할 수 있는 함수들을 가진 객체.
모델을 사용하려면 아래와 같이 사전에 스키마를 만들어주어야됨.
import mongoose from "mongoose";
const { Schema } = mongoose;
// post
const PostSchema = new Schema({
title: String,
body: String,
});
const Post = mongoose.model("Post", PostSchema);
export default Post; // 이 Post 모델 객체를 사용해 db 조작이 이루어진다
3) 모델(model) 사용
import { Post } from "./models/post"; // 만들어준 Post 사용
app.post("/post", async (req, res) => {
const { title, body } = req.body;
const post = new Post({ // 인스턴스를 만들어 사용
title: title,
body: body,
});
try {
await post.save();
res.send("db 저장 성공!", JSON.stringify(post));
} catch (err) {
console.error(err);
res.status(500).send("db 저장 실패!");
}
});
Typescript
express도 mongoose 도 공식문서가 친절해서 참 좋다.
그중 어려웠던 mongoose 의 인스턴스 메서드와 스태틱 메서드에 대해서 작성하겠다
1) 스키마 생성
const userSchema = new Schema({
userName: String,
hashedPassword: String,
});
const User = mongoose.model("User", userSchema);
export default User;
2) 인스턴스 메서드
// 인스턴스 메서드 정의
userSchema.methods.hello = function () {
return 'hello!!';
};
// 인스턴스를 만들어 사용
const user = new User({ userName: 'jack' });
console.log(user.hello()); // "hello!!"
3) 스태틱 메서드
// 스태틱 메서드 정의
userSchema.statics.findByUserName = function (userName: string) {
return this.findOne({ userName });
};
// User 모델에 바로 접근해서 사용
User.findByUserName("jack").then(name => {
console.log(name); // "jack", "jack" 이름을 가진 user 출력
});
4) Type 추가 (공식 문서 링크)
interface IUser {
userName: string;
password?: string;
hashedPassword?: string;
}
// 인스턴스 메서드 type 정의
interface IUserMethods {
hello(): string;
}
// static 메서드 type 정의
interface UserModel extends Model<IUser, {}, IUserMethods> {
findByUserName(
userName: string
): Promise<HydratedDocument<IUser, IUserMethods>>;
}
// user 스키마 type 정의
const userSchema = new Schema<IUser, UserModel, IUserMethods>({
userName: { type: String, required: true },
password: { type: String },
hashedPassword: { type: String },
});
userSchema.methods.hello = function () {
return 'hello!!';
};
userSchema.statics.findByUserName = function (userName: string) {
return this.findOne({ userName });
};
const User = model<IUser, UserModel>("User", userSchema);
export { User };
끝! 읽어주셔서 감사합니다~

'Node.js' 카테고리의 다른 글
서버pc에 파일 업로드 (3) | 2024.11.09 |
---|