프론트엔드 개발자를 위한 초스피드 익스프레스 프로그래밍

TonyHan·2022년 1월 24일
0

위키북스작업

목록 보기
3/3
post-thumbnail

https://expressjs.com/ko/api.html

기본코드

  • 설치
npm init
npx express-generator <프젝명>
npm install express cors  // express-generator 안쓰면
npm i -g nodemon  // 다른 프로젝트에서도 해당 모듈을 사용가능
  • 옵션
    package.json에 "type":"module" 서서 import from 할 수 있지만 아직 에러 많이 남

jsconfig.json 안에
"compilerOptions":{
"baseURL" : "src",
"target" : "ES6",
}

와 같은 옵션을 넣어주면 좋을 아직은 더 연구가 필요함

// essential
const express = require("express");
const cors = require("cors");
let app = express();

//routes
import indexRouter from "./routes/index.js";
import usersRouter from "./routes/users.js";

// middleware
app.use(cors({ origin: "http://localhost:3000" })); // cors처리
app.use(express.json()); // POST가 왔을때 req.body에 데이터를 담아준다
app.use(express.urlencoded({ extended: false })); //

// route control
app.use("/", indexRouter);
app.use("/users", usersRouter);
// 필수 라이브러리
const express = require("express");
const cors = require("cors");
let app = express();

// 처리 루트들
const { article, company, comment, user, board, reply } = require("./routes");
const PORT = 3000;

// 기본 설정들
app.use(cors());
//app.use(cors({origin : "http://localhost:3000"}));
// POST 때문에 필요한 옵션들
app.use(express.json()); // JSON을 잘 받아오는 미들웨어, POST가 왔을때 req.body에 담개해준다.
app.use(express.urlencoded({ extended: true })); // JSON 내의 객체를 처리할지 여부 -> 세부설정

// 기능별 라우터 추가
app.use(article);
app.use(company);
app.use(user);
app.use(board);
app.use(comment);
app.use(reply);

// `/`로 왔을때의 서버에서 확인
app.get("/", (req, res, next) => {
  res.send("Server is running!");
});

// 포트명과 리스닝이 성공했을 때 실행될 콜백 함수를 적기 -> 이건 나중에 다시 보자
app.listen(PORT, "localhost", () => {
  console.log(`App listening at http://localhost:${PORT}`);
});

방법은 위와같이 두가지인데

그냥 원하는거 쓰면된다. 나는 아래쪽 것이 좀 더 깔끔해서 많이 쓴다.

cors(cross-origin Resource Sharing) : 브라우저 쪽에서 우리가 다른 사이트 접속시 막는것으로 이 접속을 푸는게 cors. 다른 출처간의 리소스를 공유할 수 있게 하는 것. 요청을 받는 backend 쪽에서 이걸 허락할 다른 출처들을 미리 명시.
header의 origin 항목에는 scheme과 도메인, 포트가 담긴다. 여기서 scheme이란 요청할 자원에 접근할 방법(http, https, ssh 등). 보내면 답장으로 Access-Control-Allow-Origin 정보를 실어서 던진다. 그럼 크롬이 이를 origin의 header과 동일한지 확인

SOP : same-origin-policy(동일 출처 정책)으로 동일한 출처 URL끼리만 API등의 데이터 접근이 가능한거.

https://velog.io/@hyunju-song/body-parser%EC%9D%98-urlencoded%EB%8A%94-%EB%8F%84%EB%8C%80%EC%B2%B4-%EC%96%B4%EB%96%A4-%EC%97%AD%ED%95%A0%EC%9D%84-%ED%95%98%EB%8A%94-%EA%B1%B8%EA%B9%8C

urlencoded({extended : })는 어떤 옵션일까?

이를 이야기 하기 전에 우선 자바스크립트에서 데이터를 주고받는 형식에 대해서 잠깐 이야기하고 가면 좋을 듯하다.

자바스크립트에서 데이터를 주고받고 읽을 때는 객체 형태를 띄고, 실제로 데이터를 주고 받을 때도 객체 형태를 선호한다.
(따라서 여러 과정을 거쳐 파싱을 거쳐야 하는 것이다. JSON을 사용하는 것처럼)

extended 옵션의 경우, true일 경우, 객체 형태로 전달된 데이터내에서 또다른 중첩된 객체를 허용한다는 말이며, false인 경우에는 허용하지 않는 다는 의미이다.

bodyParser 미들웨어의 여러 옵션 중에 하나로 false 값일 시 node.js에 기본으로 내장된 queryString, true 값일 시 따로 설치가 필요한 npm qs 라이브러리를 사용한다.

기본구조

DB 정의

const mongoose = require("mongoose");
//import AutoIncrement from "mongoose-sequence"; //무한 스크롤
const AutoIncrement = require("mongoose-sequence")(mongoose);
let Schema = mongoose.Schema;

const Article = new Schema({
  author: { type: Schema.Types.ObjectId, ref: "User", required: true },
  title: { type: String, required: true },
  content: { type: String, required: true },
  board: { type: Schema.Types.ObjectId, ref: "Board", required: true },
  createdAt: { type: Date, default: Date.now, required: true },

  // 동적으로 변화할 수 있는 데이터
  viewCount: { type: Number, default: 0 },
  thumbupCount: { type: Number, default: 0 },
  thumbdownCount: { type: Number, default: 0 },
  commentCount: { type: Number, default: 0 },
  deleteTime: { type: Number, default: 0 },

  // (옵션) : 사용자가 게시글에 추가할 수 있는 데이터
  articleImgAddress: { type: String, default: null },
  memtion: { type: Schema.Types.ObjectId, ref: "User" },
});

Article.plugin(AutoIncrement, { inc_field: "key" }); //무한 스크롤

module.exports = Article;
  1. mongoose 가져오기
  2. mongoose.Schema를 가져와서 새로운 객체를 만듬 이게 Schema임

이때 쓸수 있는건 다양하지만 스키마는 기본 3가지로 구분해서 DB의 구조를 정의해야한다.

  1. 필수로 필요한거 + 꽤 바뀜 -> 변수 : required: true로 필수 처리해주기
  2. 필수 X + 자주 바뀜 -> static
  3. 안바뀜 -> 상수

이때 타 DB를 참조하고 싶으면
type : mongoose.Schema.Types.ObjectId, ref:"User"
로 타입은 타 DB 스키마 row의 ObjectId를 키값으로 가져오고 참조하는 DB는 가시성을 위해 ref에 적어놓는다. 안 적어도 되기는 하는데 일단 적어놓자

const mongoose = require("mongoose");
const crypto = require("crypto");
let Schema = mongoose.Schema;

const User = new Schema({
  email: { type: String, required: true, unique: true },
  hashedPassword: { type: String, required: true },
  salt: { type: String, required: true },
  createdAt: { type: Date, default: Date.now, required: true },
  nickname: { type: String, required: true, unique: true },
  company: { type: Schema.Types.ObjectId, ref: "Company", required: true },
});

// password 가상 선택자
User.virtual("password").set(function (password) {
  this._password = password;
  this.salt = this.makeSalt();
  this.hashedPassword = this.encryptPassword(password);
});

// Salt 생성 함수
User.method("makeSalt", () => {
  return Math.round(new Date().valueOf() * Math.random()) + "THISISSECRET";
});

// 해시된 비밀번호 생성 함수
User.method("encryptPassword", function (plainPassword) {
  return crypto
    .createHmac("sha256", this.salt)
    .update(plainPassword)
    .digest("hex");
});

// 사용자 인증 함수
User.method("authenticate", function (plainPassword) {
  const inputPassword = this.encryptPassword(plainPassword);
  return inputPassword === this.hashedPassword;
});

module.exports = User;

위에서 특이한 두가지 부분을 자세하게 조사해보자

// password 가상 선택자 -> 실재 성분으로 들어가 있지는 않지만 타 성분을 
User.virtual("password").set(function (password) {
  this._password = password;
  this.salt = this.makeSalt();
  this.hashedPassword = this.encryptPassword(password);
});

// Salt 생성 함수
User.method("makeSalt", () => {
  return Math.round(new Date().valueOf() * Math.random()) + "THISISSECRET";
});

User.methods.makeSalt(function() {
  return Math.round(new Date().valueOf() * Math.random()) + "THISISSECRET";
});

password만을 위해서 가상 선택자와 함수를 따로 만들었다. 이렇게 하는 이유는 password는 반드시 암호화되어서 서버에 들어가야 하기 때문이었다. 그래서 가상 선택자로 사용자가 입력한 password 데이터를 받아와 함수로 내부 데이터를 조절해 실재 Schema안에 있는 hashedPassword 안에 넣어준것이다.

이때 Schema에서 사용할 함수를 넣는 방법은 두가지가 있다. 이건 보고 학습하면 된다.

이렇게 만들어진 Schema들은 index.js로 묶여서

global에 작성된 model.js에서 사용되게 된다.

https://www.zerocho.com/category/NodeJS/post/57924d1e8241b6f43951af1a

const mongoose = require("mongoose");
const schema = require("./schema/index.js");

const db = mongoose.connection;
const model = (() => {
  db.on("error", console.error);
  db.on("open", () => {
    console.log("Connection mongodb!");
  });

  //몽고디비 앱 액세스 주소
  mongoose.connect(
    `mongodb+srv://root:비밀번호@cluster0.6upap.mongodb.net/myFirstDatabase?retryWrites=true&w=majority`
  );

  // 스키마 연결
  const model = {};
  for (let key in schema) {
    model[key] = mongoose.model(key, schema[key]);
  }
  return model;
})();

module.exports = model;

위쪽에 보면

이걸 이해하기 위해서 NoSQL을 이해하고가자

NoSQL(Mongoose)

가장 큰 차이점은 JOIN이 없다. 하나의 쿼리로 여러 테이블을 합치는 작업이 항상 가능하지 않다. 동시에 쿼리를 수행하는 경우 쿼리가 섞여 예상치 못한 결과를 낼 가능성이 있다는 것도 단점

그래서 정보를 일관성 있게 전달되어야 하는 예약 처리 부분은 MySQL
빅데이터, 메시징, 세션 관리 등에는 확장성과 가용성을 위한 몽고디비

https://www.mongodb.com/try/download/compass
몽고디비 관리도구

  • Mongoose(MySQL의 시퀄라이즈 같은거)
    ODM(Object Document Mapping)

routes

routes는 db를 컨트롤 하기 위한 CRUD 기반의 API들이 정의되어 있는 곳이다. 말은 어렵게 했지만 주소창에 주소입력에 따라 다른 처리를 하게끔 하는 곳이다.

profile
신촌거지출신개발자(시리즈 부분에 목차가 나옵니다.)

0개의 댓글