반응형

Build Backend

Babel-node는 실제로 사용되는게 아니라 개발할 때 사용되는것이라. 이것을 실제사용도에 맞게 전환시키는 Babel-CLI를 사용해야한다.

Bable-CLI를 설치 후에는 package.json에 build 명령어를 작성해준다.

"build":"babel src/init.js -d build"
// -d는 빌드한 코드를 추후에 나오는 글로 통해 어디에 저장할지 알려줌

 

typescript같은경우 tsconfig.json에 아래를 추가하며 "exclude"를 통해 빌드를 하지 않게 지정할 수 있다.

"compilerOptions":{
	"outDir": "./build/",
}

package.json에 다음과 같이 입력후 저장후 콘솔입력하면 build폴더에 build가 된다.

"build": "tsc --build",

 

다음 npm 명령어 start를 생성하고 실행시켜 잘동작하는지 확인해준다.

"scripts":{
	"start":"node build/init.js"
}

 

빌드가 다 되었으면 백엔드 서버를 실행시켜주는 배포서버를 찾아야한다. 

render.com 과 cloudtype이 있는데 매우 사용이 쉽다.

 

MongoDB Atlas

백엔드 데이터를 저장하기 위해 MongoDB Atlas를 사용해야한다. 이곳은 파일 타입을 제외한 글자 및 숫자 타입의 데이터를 저장한다.

  1. MongoDB Atlas을 가입
  2. Project에서 New Project
  3. 프로젝트 이름 작성 및 생성 버튼을 누른다.
  4. Create a Deployment에서 Create를 누르고 요금제를 설정
  5. Overviewdptj Connect를 눌러 MongoDB의 서버를 복사해주고 다음을 눌러 password를 잘 저장해준다. password는 잊어버리면 안되니 잘 백업해두자. 주어진 MongoDB 주소에 "<password>"를 지워주고 password입력한 백엔드 배포에 DB_URL환경변수를 기입하자
  6. git push를 하고 다시 배포하게 되면 데이터베이스가 만들어진다.

AWS

파일을 저장하기 위해 AWS를 사용할 것이다.'

AWS에 가입을 하고 AWS S3로 이동한다. 

버킷 생성을 누르고 버킷 이름을 지어주어야하는데 AWS에서 유일한 이름이여야 한다.

모든 public 접근을 막기위해 block all public을 체크하고 생성을 누른다.

 

두번째는 API_KEY를 만들어주어야 하는데ㅐ 그래야 NodeJS와 AWS가 서로 대화할수 있다.

검색에서 IAM를 검색한다 IAM은 API KEY를 만들 수 있게 해준다.

사용자를 누르고 사용자를 생성한다.

사용자 이름을 작성하고 다음을 눌러 권한 설정을 해준다.

s3에 대한 권한만 줄것이기에 AmazonS3FullAccess를 찾아서 눌러주고 다음을 누르고 생성을 눌러준다.

사용자 이름을 누르고 엑세스 키 생성을 누르고 CLI 누르고 설명 태그 작성후 만들기 누르면 키와 비밀키가 나온다. 비밀키를 잃어버리면 재발급 받아야하니 잘 보관해두자

 

이제 파일을 AWS로 보내주는 작업을 해줘야하는데 저장을 할 수 있게 만든 미들웨어를 수정해야한다.

npm i multer-s3
npm install @aws-sdk/client-s3

 

두개를 설치해주고 다음과 같이 작성

 

const s3 = new S3Client({
	region: "ap-northeast-2",
	credentials: {
		accessKeyId: process.env.AWS_ID + "",
		secretAccessKey: process.env.AWS_SECRET + "",
	},
});

const multerUploader = multerS3({ s3: s3, bucket: "버킷이름" });

export const avatarMulter = multer({
	dest: "uploads/avatar/",
	limits: {
		fieldSize: 3000000,
	},
	storage: multerUploader,
});

export const videoMulter = multer({
	dest: "uploads/video/",
	limits: {
		fieldSize: 10000000,
	},
	storage: multerUploader,
});

 

이렇게 해서 간다하게 파일을 올리게 되면 반응은 없지만 AWS에는 무언가 올라가 있는 모습이 보여진다. 

이것을 보이게 하기위해서는 해당 버킷의 permissions(권한) 작업을 해야하는데 버킷을 만들 때 모든 Public접근을 막아두었던것을 

// 체크해제
새 ACL(액세스 제어 목록)을 통해 부여된 버킷 및 객체에 대한 퍼블릭 액세스 차단
임의의 ACL(액세스 제어 목록)을 통해 부여된 버킷 및 객체에 대한 퍼블릭 액세스 차단


// Access Control List (엑세스 제어 목록)을 제공할 때 공개권한 을 주는 것

위 둘을 제외한 나머지는 체크를 하고 확인을 작성후 눌러준다.

 

그리고 다시 MulterS3로 Access Control List를 전달해주어야 한다. acl은 object의 권한이다.

const multerUploader = multerS3({
	s3: s3,
	bucket: "squaresquare",
	acl: "public-read",
});

 

만약 
The bucket does not allow ACLs
이 나왔다면

권한 -> 객체 소유권 편집 -> ACL 비활성화됨(권장)을 ACL 활성화됨로 변경 ->
ACL이 복원된다는 것을 확인합니다. 체크 -> 버킷 소유자 선호 체크 -> 변경사항 저장
위의 방법까지 해보시고, 그래도 안 되시는 분들은 
ACL(액세스 제어 목록)에서 편집->모든 사람(퍼블릭 액세스)에 나열, 읽기 체크해주신 후 
변경사항 저장해서 테스트

 

이렇게 되었을때 기존 로컬스토리지를 사용하는 것이 아닌 스토리지를 사용하는 것이기 때문에 회원 수정을 통한 아바타 이미지에 대한 경로도 재 설정 해주어야 하고 view파일에 있는 소스들도 다시 확인을 해야한다.

 

Bucket의 폴더화

const s3VideoUploader = multerS3({
	s3: s3,
	bucket: "squaresquare",
	acl: "public-read",
	key(req, file, callback) {
		callback(null, "videos/" + file.originalname);
	},
});
const s3ImageUploader = multerS3({
	s3: s3,
	bucket: "squaresquare",
	acl: "public-read",
	key(req, file, callback) {
		callback(null, "images/" + file.originalname);
	},
});

 

DevServer와 LiveServer 개발환경 분할

백엔드를 배포하면 process.env.NODE_ENV를 받아 올 수 있다. 로컬의 환경에서는 undefined가 나오지만 대게 백엔드를 운용하는 곳에서는 'production'을 줄것이다. 이를 통해 로컬 환경과 실제 운용되는 서버를 확인하여 로컬로 바로 업로드할지 저장소로 업로드 할지 정할 수 있다. 아래의 변수로 Controller 및 Multer 에서 파일을 업로드하는 곳에 삼항연사자를 통해 업로드를 제어하자

const isRenderDotCom = process.env.NODE_ENV === "production";
728x90
반응형

유저의 개인정보를 수정하는 페이지를 만들기 앞서 Protect Middle ware를 만들어줘야한다. 

로그인한 유저들을 위한 페이지에 로그인하지 않는 유저는 login router로 보내주는 미들웨어

로그인한 유저들이 다시 login page로 보내주지 않는 미들웨어가 필요하다. 

미들웨어를 만들어주고 라우터에 미들웨어를 사용해주자 전역 미들웨어와 달리 특정 라우터에만 적용할꺼라 라우터를 작성한 파일에 불러올것이다.

userRouter.get("/github/join", publicOnlyMiddleWare, joinWithGithub);
userRouter.get("/github/joinfin", publicOnlyMiddleWare, joinWithGithubFin);
userRouter
	.route("/mypage")
	.all(protectMiddleWare)
	.get(getEditUser)
	.post(postEditUser);

 

Edit user

Edit user의 핵심은 바뀐 상태가 있으면 req.session에 있는 유저의 정보도 같이 바꿔주어야한다.

 

Multer

파일 업로드를 도와줄 미들웨어 multer

form에 attribute에 아래를 입력해야한다. 이 것은 form이 다르게 encode될 것이다. 이게 파일을 업로드 하기 위한 유일한 조건이다.

enctype="multipart/form-data"

multer를 사용할 수 있게 미들웨어로 만들어줘야한다. 또한 파일을 저장하는 폴더를 지정해줘야한다. 

multer({
	dest:"파일 저장하는 폴더"
    limits:{
    	fileSize: byte 단위
        },
})

사용할 특정 라우터의 post에 가서 미들웨어를 작성해준다. 하나의 파일을 보내기 위해서 single로 작성하고 그게 form에서 어디로 부터오는지 input의 이름을 작성해준다.

globalRouter
	.route("/upload")
	.get(upload)
	.post(uploadMulter.single("contentUpload"), uploadPost);

controller에가서 req.file을 사용한다. 그리고 req.file은 req.file.path를 통해 파일의 위치를 찾아낼수있다.

주의사항 ** 절대 파일은 백엔드 서버 및 DB에 직접 저장하지 않고 파일의 위치를 저장한다. 

Appreance Avatar Image

avatar 이미지를 넣고 브라우저에 봤을 때 이미지source의 경로는 유효한데 나오지 않았을때가 있다. 

이것은 우리의 이미지가 들어있는 폴더가 브라우저에 노출이 되어있지 않아 GET하지 못하기 때문이다. 이미지를 새창으로 열어보면 에러문구를 볼 수 있다. 이를 위해 우리가 해야하는 일은 해당 폴더 전체를 브라우에 노출시켜주는 것이다. 이를 static files serving 라고 한다.

 

Static files Serving의 설정을 하기 위해서는 라우터를 손바주면 된다.

//server.js or ts
app.use("/uploads", express.static("uploads"));

Connect UserModel & ContentsModel

contentsModel에 owner를 추가하는데 이때 owner의 타입은 id가 되어야한다. 하지만 id를 입력하기 위해서는 mongoose를 통해 값을 작성해주어야한다. 그리고 이것을 참조하는 곳을 ref를 통해서 명시해주어야한다. 

owner: { type: mongoose.Schema.ObjectId, ref: "User" },

ref 를 지정해주면 아래의 populate 메소드를 통해 세부내용을 덧붙일수있다.

const video = await contentsModel.findById(id).populate("owner");

 

유저 스키마에도 비디오의 아이디를 array로 저장하면 불러올때 편하게 불러올수있다.

 

주의사항** 비디오의 아이디를 유저스키마에 저장할때 비밀번호도 다시 저장이 되는 경우가 있어 로그인할때 로그인이 되지 않는 경우가 있기 때문에 이러한 경우를 막아주는 작업을 진행 해주어야한다.

export const uploadPost: ExpressRouter = async (req, res) => {
	const {
		session: { user },
		body: { contentRadio, title, description, hashTags },
		file,
	} = req;

	try {
		if (contentRadio === "video") {
			const newVideo = await contentsModel.create({
				contentsForm: contentRadio,
				title,
				description,
				fileUrl: file?.path,
				hashTags: contentsModel.formatHash(hashTags),
				owner: user?._id,
			});
			const findUser = await userModel.findById(user?._id);
            
            //
			findUser?.videos?.push(newVideo._id);
			findUser?.save();
            // => 이부분에서 비밀번호가 다시 저장된다.
            
			return res.redirect("/video");
		}
	} catch (error) {
		console.log(error);
		return res.status(400).render("upload", {
			pageTitle: "UPLOAD",
			error,
		});
	}
};

따라서 패스워드만 변화가 있을 때 패스워드만 저장할 수 있게 userSchema에서 수정해주어야한다.

userSchema.pre("save", async function () {
	if (this.isModified("password")) {
		this.password = await bcrypt.hash(this.password!,해싱 수);
	}
});
728x90
반응형

social login

github login

먼저 developer setting에 들어가서 Oauth 앱을 만들어준다. authorization callback URL은 본인 페이지 뒤에 콜백할 url을 맘대로 지어도 된다. 예) http://localhost:9999/user/github/login

 

Client ID와 Client Secrets은 .env파일에 넣어준다. 특히 client screts은 잘못 기재하면  다시 generate 해야하니 귀찮은 일을 만들지 않도록 잘 기입해준다.

 

https://docs.github.com/ko/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps

 

OAuth 앱 권한 부여 - GitHub Docs

다른 사용자가 OAuth app에 권한을 부여하도록 설정할 수 있습니다.

docs.github.com

구현 방법 (주요사항만)

1. 깃헙으로 로그인을 하고 싶다면 사용자로 깃헙으로 보내준다.

핵심적인 부분을 살펴보자면 먼저 github id 요청으로 접속을 해야한다. 접속과 동시에 매개변수를 보내주어야하는데 앱을 만들었을 때 client_id를 보내주어야 404페이지가 안나온다. scope를 설정하면 가입하고자하는 깃허브 유저의 정보 제공을 설정할 수 있다. 필요한것만 가져오면 된다.

 

object를 url로 바꾸는 방법

	const cfg = {
		charater: "cho-gal",
		scope: "weapon:hands spieces:oger",
	};
	const param = new URLSearchParams(cfg).toString();

	console.log(param);

 

2.정보를 공유하는 것을 승인하게 되면 깃헙은 사용자를 개발하고 있는 사이트로 돌려보낼것이다.

이때 깃헙에서 코드를 주는데 이것을 Access token으로 바꿔야한다.

post 처리를 위해 fetch를 사용해준다. 하지만 백엔드에서는 라이브러리 이용하지 않으면 fetch를 할 수가 없다. node에서 fetch를 사용하기 위해서는 node-fetch같은 라이브러리를 설치하고 이것을 fetch로 import시키고 json데이터를 받아오면 된다.

 

3. 깃헙은 사용자를 token과 함께 redirect시킬것이다.

이제 access_token을가지고 github api을 사용해 user정보를 가져와야한다.

 

user 정보를 가져왔는데 이메일이 null 일 경우 아래 url를 참고

https://docs.github.com/en/rest/users/emails?apiVersion=2022-11-28

 

 

Emails - GitHub Docs

Use the REST API to manage email addresses of authenticated users. If a request URL does not include a {username} parameter then the response will be for the signed-in user (and you must pass authentication information with your request). Additional privat

docs.github.com

 

 

소셜 로그인에 대한 mongoose 스키마 모델을 재정의 할 필요가 있다. 또한 소셜 로그인 한 유저에게 세션을 주는 것도 잊지말자

 

LogOut

get 요청을 하는 /logout 라우터를 만들고 미들웨어로 req.session.destroy() 끝. 세션을 없애주면 된다. 그리고 홈으로 리다이렉트.

728x90
반응형

이번 NodeJS 공부중 가장 중요한 부분.

User

user를 만드는 건 전에 작성했던 CRUD와 동일하다.

하지만 user를 만드는 데 있어 제일 중요한것 중에 하나가 개인의 개인정보를 보호하는 것이다. 그 중 password가 가장 민감한 부분일 것이다. 절대 db에 password를 있는 그대로 정장하면 안된다. password를 암호화해야한다.

Hashing

password를 암호화하는데 있어 Hashing이라는 기능을 사용하면 우리가 즐겨사용하는 비밀번호의 노출을 방지할수있다. 이를 사용하게 되면 예를 들어 Input 값이 1234라고 할때 Output은 'fqo9wiefn!@#Fqqiwoefj' 이런식의 변화가 이루어진다. 동일한 입력값은 동일한 출력값으로 나온다. 또한 기존 비밀번호의 순서를 1243 라고 바꿔준다면 'qwefjoqwenmfojqn!@#1230n' 전혀다른 비밀번호로 hashing이 된다. 하지만 반대로 output을 input값으로 넣는다면 기존 input값으로 나오지 않는다.

하지만 'Rainbow Table'라는 것이 있는데 이것을 가지고 역추적을해서 입력값을 얻어낼 수 있다. 하지만 이런것들 때문에 'Salt'라는 것이 생겼다. Salt는 랜덤 텍스트인데 비밀번호가 심플하더라도 Salt가 추가되어 password에 저장된다. 이는 Rainbow Table에도 없는 값이 된다. 이렇게 되면 비밀번호를 찾게 되는것도 어려워지는데 우리가 비밀번호 찾기 했을때 기존 비밀번호가 없어지고 다른 값을 받거나 바로 바꿀수있는 페이지로 이동하게 되는 경우가 바로 이런 경우로 인해 그렇다.

 

Bcrypt

https://www.npmjs.com/package/bcrypt

 

bcrypt

A bcrypt library for NodeJS.. Latest version: 5.1.1, last published: 4 months ago. Start using bcrypt in your project by running `npm i bcrypt`. There are 4269 other projects in the npm registry using bcrypt.

www.npmjs.com

npm i bcrypt

 

Status Code

회원가입 실패시 Status code를 함께 설정하여 브라우저가 실패를 인식하게 만들어줘야한다. 브라우저가 히스토리가 남지않게 이러한 조취들을 취해주어야한다.

	if (userNameExists) {
		return res.status(400).render("join", {
			pageTitle: "JOIN",
			idError: "아이디가 이미 사용중입니다.",
		});
	}

 

Input으로 들어온 비밀번호 Hashing 된 비밀번호 확인

await bcrypt.compare(password, user.password);

// 유저가 처음 입력한 비밀번호 , 해싱된 비밀번호

 

Sessions and Cookies

유저를 기억하게 만드는 방법. 브라우저가 너가 누군지 알게 만들어줘야한다. 

그 방법 중 한가지는 유저에게 Cookies를 보내주는 것이다. 

쿠키를 이해하기 앞서 Session부터 알아야한다.  Session은 브라우저와 백엔드 사이의 memory , history같은 것이다.

Session을 작동시키려면 백엔드와 브라우저가 서로에 대한 정보를 가지고 있어야한다. 따라서 로그인을 할때 자그마한 텍스트를 주게 되는데 이것을 가지고 유저에 대한 정보를 가질수 있게 된다. 

이 때 세션을 처리할 수 있도록 미들웨어를 만들어줘야하는데 express를 사용한다면 아래의 패키지가 도움을 줄것이다.

npm i express-session

 이것을 서버에 import 시키고 router들 가장 앞에 app.use를 이용해 사용해준다.

app.use(session({ secret: "hoho", resave: true, saveUninitialized: true }));

// resave
// 세션이 수정되지 않았더라도 요청이 왔을 때 세션을 다시 저장할 지 여부를 결정합니다.

// saveUninitialized
// 세션이 새로 만들어지고 수정된 적이 없을 때 uninitialized
// 쉽게 말해 새로운 세션이 있는데 수정된 적이 없으면 최화되지 않는 것이다.
// 세션을 수정할 때만 세션을 DB에 저장하고 쿠키를 넘겨주는것

//gpt
// 세션이 초기화되지 않은 상태에서 요청이 왔을 때 세션을 저장할지 여부를 결정합니다.
// true로 설정하면 초기화되지 않은 세션이 있는 요청에 대해 새로운 세션을 저장하게 됩니다. 
// 이것은 사용자가 세션을 사용하기 전에 세션을 초기화하는 데 도움이 됩니다.

secret은 아무도 모르는 string으로 작성하게 될것이다. 

app.get("/see-session", (req, res) => {
	return res.send(req.session.id);
});

express는 알아서 그 브라우저를 위한 세션 id를 만들것이다.

 

이제 브라우저에게 어떤 사람이 로그인 했는지 세션에 넣어줘야한다. 세션저장소에 세션을 보면

app.use((req, res, next) => {
	if (req.sessionStore && typeof req.sessionStore.all === "function") {
		req.sessionStore.all((err, sessions) => {
			console.log(sessions);
			next();
		});
	}
});

오브젝트를 받을 수 있는데 세션아이디와 쿠키가 적혀져있는 오브젝트를 받을 수 있다. 여기에 유저의 정보를 넣어주면 된다. 넣는 방법은 간단한데. 로그인 시에 넣어주면 된다. 작성한 로그인 컨트롤러에 가서  아래와 같이 작성한다.

const session = req.session;
	session.loggedIn = true;
	session.user = user;

타입스크립를 사용해서 작성하게 되면 express-session에는 loggedIn과 user에 대한 타입은 명시되지 않아 오류가 날수 있다. 따라서 이를 해결하기 위해 두가지 방법이 있는데 express-session 파일에 SessionData 타입에가서 직접 타입을 명시해주는 방법이 있고 declare module을 사용해서 명시해주는 방법이 있다.

 

이를 이용해 FrontEnd에서 유저가 로그인하면 Navgation Bar에 있는 login 과 logout에 대한 내용을 보여지게 혹은 숨기게 할 수 있는데  pug에서는 req.session에 직접 전달하는 것이 아니면 접근하는게 어렵다. 이런 경우에는 res.locals를 사용해서 접근할 수 있다. 이것을 전역으로 사용가능한 미들웨어로 만들어준다.

// server.ts

app.use(middlewarename)

// middleware.ts

export const pugLocalMiddleware: ExpressRouter = (req, res, next) => {
	res.locals.loggedIn = req.session.loggedIn ?? false;
	res.locals.loggedUser = req.session.user ?? null;
	console.log(res.locals);
	next();
};

 

Session Data

express-session을 사용하면 Session Id는 쿠키안에 저장이 되는데 Session Data는 쿠키안에 저장이 안되어 있다. session data는 서버쪽에 저장이 된다. 서버에 저장되는 default session storage는 휘발성이고 실제로 사용하기 위한 것은 아니다. 그래서 이것을 실질적으로 사용하려면 DB에 Session을 저장할 수 있게 session store를 사용해야한다. 이를 이용하면 서버가 재부팅을 하더라도 저장된 session data가 있으면 지속적인 로그인을 할 수 있다.

 

하지만 session을 DB에 모두 저장을 하는 것은 좋은 생각이 아니다. 익명의 유저에게는 주지 않고 로그인한 유저들에게 만주는 것이 바람직하다. 

 

이걸 위한 해결책이 있는데 바로 token authentication이다. 

예를 들어 핸드폰 앱을 만들 때 이것들은 쿠키를 갖지 않게 때문에 token을 사용해야한다. 하지만 브라우저이기 때문에 token을 사용해도 되고 안해도 된다.

 

Cookie Property

Secret은 우리가 쿠키에 sign할 때 사용하는 string. 쿠키에 sign하는 이유는 backend가 쿠키를 줬다는 걸 보여주기 위함. 왜냐면 session hijack이라는 공격 유형이 있기 때문이다. 이건 길고 강력하며 무작위로 작성되어야 한다.

 

Domain은 쿠키를 만든 backend가 누구인지 알려준다. 브라우저는 Domain에 따라 쿠키를 저장하도록 되어 있다. 그리고 쿠키는 Domain에 있는 backend로만 전송이 된다.

 

Expires은 쿠키의 만료 날짜이다. 만료날짜를 지정하지 않으면 session cookie로 설정이되고 사용자가 닫으면 session cookie는 끝나게 된다. Max-Age는 언제 세션이 만료 되는지 알려준다.

app.use(
	session({
		secret: "",
		resave: false,
		saveUninitialized: false,
		store: MongoStore.create({
			mongoUrl: "",
		}),
        cookie:{
            maxAge: 밀리세컨드
        }
	}),
);

 

Environment Variables(환경변수)

Root 디렉토리에  '.env' 파일을 만들고 환경변수에 대한 선언을 해준다. 변수의 key 값은 대문자로 이루어져야한다.

KEY_NAME=어쩌구저쩌구

이것을 사용하기 위해서는 process.env.KEY_NAME 과 같이 사용해야한다.

만약 오류가 났다면 dotenv를 설치를 해야한다. 또한 서버가 시작되는 제일 처음 시작점에 import나 require문을 사용하여 config를 불러와야한다. 타입스크립트를 사용하게 되면 타입을 설정해줘야한다. as 키워드나 non - null assertion 연산자인 (!) 느낌표를 활용하는 것이 보기에 좋아보인다.

728x90
반응형

Update

export const videoEditSave: ExpressRouter = async (req, res) => {
	const { id } = req.params;
	const { title, description, hashTags } = req.body;
	const videoId = id.replace("/edit", "");
	const video = await contentsModel.exists({ _id: videoId });

	if (!video) {
		return res.render("404", {
			pageTitle: `Not Found`,
		});
	}
	await contentsModel.findByIdAndUpdate(videoId, {
		title,
		description,
		hashTags: hashTags.split(",").map((word: string) => `#${word}`),
	});

	return res.redirect(`/video`);
};

 

- Mongoose - Pre

Pre는 새로운 객체를 저장할때 저장하기전 스키마의 행위를 지정해주는 미들 웨어이다.

예를 들면 해쉬태그를 작성한다면 input의 값을 쉼표로 나누어 작성했을 경우 하나의 객체로 만들어주며 하나의 객체 앞에 '#'을 붙여주는 행위를 한다 가정했을 때 아래와 같이 문장을 작성할 수 있다. 

Pre에는 두가지 인자를 받는데 첫번 째로는 방식이며 지정되어 있다. 두번째는 function을 입력해주면 된다.

contentsSchema.pre("save", async function () {
	this.hashTags =
		this.hashTags &&
		(this.hashTags[0] as string).split(",").map((word) => `#${word}`);
});

 

- Mongoose - Static

Static은 Pre와 비슷하지만 직접 function handler를 만들어준다고 생각하면 쉽다.

 

contentsSchema.static('hashFormat', function(hashTags){
	return hashTags.split(",").map((word:string) => `#${word}`)
})

 

//type script 경우에
import mongoose, { Model } from "mongoose";
import { IVideo } from "../types/type";
interface IHashformat extends Model<IVideo> {
	formatHash(hashTags: string): string[];
}
const contentsSchema = new mongoose.Schema<IVideo, IHashformat>({
	contentsForm: { type: String, required: true, trim: true },
	title: { type: String, required: true, trim: true, minLength: 3 },
	description: { type: String, required: true, trim: true, maxLength: 30 },
	createAt: { type: Date, required: true, default: Date.now() },
	hashTags: [{ type: String, trim: true }],
	meta: {
		views: { type: Number, required: true, default: 0 },
		rating: { type: Number, required: true, default: 0 },
	},
});


contentsSchema.static(
	"formatHash",
	function formatHash(hashTags: string): string[] {
		return hashTags.split(",").map((word: string) => `#${word}`);
	},
);
const contentsModel = mongoose.model<IVideo, IHashformat>(
	"Video",
	contentsSchema,
);
export default contentsModel;

 

Delete

export const deleteVideo: ExpressRouter = async (req, res) => {
	const { id } = req.params;
	const videoId = id.replace("/delete", "");
	const video = await contentsModel.exists({ _id: videoId });
	if (video) {
		await contentsModel.findByIdAndDelete(videoId);
		res.redirect("/video");
	} else {
		return res.render("404", {
			pageTitle: `Not Found`,
		});
	}
};

 

 

728x90
반응형

CRUD

이제 데이터베이스와 함께 사용하기 앞서 이를 활용하기 위해 CRUD를 먼저 이해할 필요가 있다.

CRUD는 아래의 약어로 이루져 있다. 

  • Create
  • Read
  • Update
  • Delete

Post와 Get을 활용한 데이터 통신을 하나 씩 이루어 나갈 것이다.

 

CRUD를 활용하기 위해서 일단 데이터의 model을 설정해주어야한다.

현재 mongoDB와 mongoose를 사용하기 때문에 mongoose로 mongoDB에게 우리가 어떤 데이터를 사용하고 있는지 알려줘야한다.

그러기에 앞서 우리의 데이터가 어떤것을 가지고 있고 어떤 타입을 지정해줘야할지 고민해 볼필요가 있다. 이런 생김새를 보통 Schema라고 한다.

Mongoose Schema & Export  Model

타입정의도 가능하며 유효성도 체크도 가능하며 디폴트 값을 줘 문장을 간소화도 할수있다. 

import mongoose from "mongoose";

const videoSchema = new mongoose.Schema({
	title: String,
	description: { type: String, required: true },
	createAt: { type: Date, required: true, default: Date.now() },
	hashTags: [{ type: String }],
	meta: {
		views: Number,
		rating: Number,
	},
});

const videoModel = mongoose.model("Video", videoSchema);
export default videoModel;

 

 

보내질 데이터를 어떤 모습으로 보내줄건가에 대한 것은 schema에 담고 이 schema model이름은 Video로 지어 import할 수 있게 만들어준다. 그리고 이를 사용하기 위해 server에 import 해준다.

 

Schema Type

https://mongoosejs.com/docs/schematypes.html

 

Mongoose v8.0.3: SchemaTypes

SchemaTypes SchemaTypes handle definition of path defaults, validation, getters, setters, field selection defaults for queries, and other general characteristics for Mongoose document properties. You can think of a Mongoose schema as the configuration obje

mongoosejs.com

 

Using Model 

Export한 videoModel을 사용하고자하는 Controller에 import를 해주자

https://mongoosejs.com/docs/queries.html

 

Mongoose v8.0.3: Queries

Queries Mongoose models provide several static helper functions for CRUD operations. Each of these functions returns a mongoose Query object. A mongoose query can be executed in one of two ways. First, if you pass in a callback function, Mongoose will exec

mongoosejs.com

위의 문서를 보고 관련된 메소드를 찾아 문장을 구성해주면 된다.

export const video = async (req, res) => {
	const videos = await videoModel.find({});
	return res.render("./videoTemp/video", { pageTitle: "VIDEO", videos });
};

 

Create Models

try catch 문을 사용하면 더 좋다.

생성을 하게되면 생성한 object는 고유 id가 저절로 제공이 된다!

https://www.mongodb.com/docs/manual/reference/method/ObjectId/

export const uploadPost: ExpressRouter = async (req, res) => {
	const { contentRadio, contentTitle, contentDesc, contentHashTags } = req.body;
	if (contentRadio === "video") {
		// const video = new videoModel({
		// 	title: contentTitle,
		// 	description: contentDesc,
		// 	hashTags: contentHashTags.split(",").map((tags: string) => `#${tags}`),
		// 	createAt: Date.now(),
		// 	meta: {
		// 		views: 0,
		// 		rating: 0,
		// 	},
		// });
		// await video.save();
		await videoModel.create({
			title: contentTitle,
			description: contentDesc,
			hashTags: contentHashTags.split(",").map((tags: string) => `#${tags}`),
			createAt: Date.now(), // schema에 default값을 주면 생략 가능
			meta: {
				views: 0,
				rating: 0,
			},
		});
	}

	return res.redirect("/");
};
728x90
반응형

Database

데이터베이스에 데이터를 올리기 위해서는 일단 Get과 Post에 대해 알아야한다. 이를 위해서 html의 form을 이용해서 POST를 보내주어야한다.

이를 이해하기 위해 Fake data base를 만들어보자

// Data
const videos: IContentsModel[] = [
	{
		id: 1,
		title: "hoho",
		desc: "fhj",
		createAt: new Date().toLocaleDateString(),
	},
	{
		id: 2,
		title: "heeh",
		desc: "werte",
		createAt: new Date().toLocaleDateString(),
	},
	{
		id: 3,
		title: "hahah",
		desc: "ryftujkty",
		createAt: new Date().toLocaleDateString(),
	},
	{
		id: 4,
		title: "asdf",
		desc: "asfdasdf",
		createAt: new Date().toLocaleDateString(),
	},
];

그리고 front를 만들어주어 back으로 보낼 준비를 해주자. 여기서 중요한 것은 form의 메소드를 POST로 설정해주어야한다. 만약 url의 설정을 바꾸고 싶다면 action attribute를 주어 url도 설정할 수 있으나 같은 url주소로 POST를 할꺼니 action은 따로 지정하지 않았다.

extends ../layout/layout
block contents 
    div 
        form(method="POST") 
            legned(hidden) EDIT #{video.title}
            label(for="videoTitle") EDIT #{video.title}
            input#videoTitle(type="text", name="title" value=video.title) 
            input(type="submit" value="submit")

 

해당 화면을 볼수 있게 라우터와 controller middleware를만들어준다.

 movieRouter.get("/:id([0-9]/edit)", videoEdit);
export const videoEdit: ExpressRouter = (req, res) => {
	const { id } = req.params;
	const videoId = +id.replace("/edit", "") - 1;
	const video = videos[videoId];
	return res.render("./videoTemp/videoEdit", {
		pageTitle: `EDIT ${video.title.toUpperCase()}`,
		video,
	});
};

POST를 위한 Router를 만들어준다.

movieRouter.post("/:id([0-9]/edit)", videoEditSave);

 

 

movieRouter.get("/:id([0-9]/edit)", videoEdit);
movieRouter.post("/:id([0-9]/edit)", videoEditSave);

 

router 설정에 get 과 post를 한 줄로 정리할 수 있는데

movieRouter.route("/:id([0-9]/edit)").get(videoEdit).post(videoEditSave);

로 축약할 수 있다.

 

그리고 POST의 controller middleware 설정을 해주면 끝

export const videoEditSave: ExpressRouter = (req, res) => {
	const { id } = req.params;
	const { title } = req.body;
	const videoId = +id.replace("/edit", "");
	const video = videos[videoId - 1];
	video.title = title;
	return res.redirect(`/video/${videoId}`);
};

 

MongoDB

mongoDB는 다목적이고 document(문서) 기반으로 작동한다. 보통 database는 문서기반이 아니다 sql 기반이다. 엑셀시트 같은 개념이다. mongoDB에서 DB는 object로 움직인다. 행으로 저장이 되지않고 Array Object들로 구성이 된다. JSON처럼 저장된다는 느낌이라고 생각하면 된다.

Install

MongoDB Community Server를 다운 받아준다. 

Connecting

Mongoose

mongoose는 NodeJS와 MongoDB를 이어주는 매개체가 될것이다. mongo가 잘 설치 되었는지 확인하기 위해 터미널에 아래의 명령어를 입력해준다. not found 문구가 나오면 다시 설치해야한다.

mongod

그리고 아래 입력

mongosh
mongosh 명령어

help
show dbs
db.

 

VScode로 돌아와서 mongoose 설치

npm install mongoose --save

mongoDB와 연결해주기 위해 코드를 작성해야한다. src 폴더에 db.ts or js를 생성

import mongoose from "mongoose";
//mongosh에 나와있는 connecting to 참조
mongoose.connect("mongodb://로컬호스트:포트넘버/임의 저장소 이름을 지정",{});

백엔드 파일에가서 db.ts or js 파일 자체를 import를 해주어야한다. 경고나 주의가 터미널창에 나오면 ,{} 안에 터미널창에 나오는 데로 작성해주면 된다.

연결의 성공 여부나 에러를 console.log로 출력하기 위해 다음과 같이 문장을 추가한다.

mongoose.connection.once("connected", () => console.log("✅ DB is connected"));
mongoose.connection.on("error", (error) => console.log(error));
728x90
반응형

Router

라우터는 controller와 url관리를 쉽게 해준다.

 

Router 구성

라우터를 구성하기에 가장 먼저 생각해야할 것은 데이터이다.

어떤 종류의 데이터를 사용할건지 정해야한다 예를들면

"/" => Home

"/music" => Music
"/music/upload" => Music upload
"/music/:id" => about id of Music

"/video" => Video
"/video/:id" => to see Video
"/video/upload" => upload Video

"/user" => User
"/user/edit" => Edit user
"/user/:id" => show other User

 

Express에서 Router 생성

const router = express.Router()

Router사용

const globalRouter = express.Router();
const musicRouter = express.Router();
const userRouter = express.Router();

app.use("/", globalRouter);
app.use("/music", musicRouter);
app.use("/user", userRouter);

// "/"
globalRouter.get("/", (req, res) => {
	return res.send("1");
});

// "/music"
musicRouter.get("/", (req, res) => {
	return res.send("2");
});

// "/user"
userRouter.get("/", (req, res) => {
	return res.send("3");
});

// "/user/info"
userRouter.get("/info", (req, res) => {
	return res.send("info");
});

 

Router Refactor..

위와 같이 작성하게 되면 코드가 길어지니 파일을 만들어 해당 Router를 불러오게 만들어주면 관리도 용이하게 된다.

// src / routers / golobalRouter.js or ts

import express from "express";

export const globalRouter = express.Router();

globalRouter.get("/", (req, res) => {
	return res.send("1");
});

 

router 뿐만 아니라 controller도 따로 파일 을 만들어 관리를 해주는 것이 좋다. 함께 두어도 작동은 하겠으나 파일의 목적과 관리의 성향이 서로 다르기 때문에 따로 폴더를 만들어주는 것이 좋다.

 

URL Parameters

/:id에서 :id 가 parameter이다. parameter가 없다면 같은 카테고리의 페이지들에 대한 라우터들을 만들어줘야한다. 파라미터가 있기에 /video/1, /video/2,/video/3/video/4,/video/6/video/2222 같은 것들이 가능하다.

 

":" 가 무조건 있어야한다 없다면 그것은 그냥 텍스트 일뿐이다.

 

//videoRouters.js or ts

import express from "express";
import { video } from "../controllers/videoController";

const movieRouter = express.Router();

movieRouter.get("/", (req, res) => {
	return res.send("movie");
});
movieRouter.get("/:id", video);
export default movieRouter;



//videoController.js
export const video= (req, res) => {
	console.log(req.params); // /video/123123 => {id: 123123}
	return res.send("video");
};

주의 해야할 점은 Express에서 변수를 활용한 "/:id" 같은 router들은 "/asdfasd" 일반적인 텍스트 라우터보다 아래에 명시 되어야한다. 이것을 해결하기위해서는 텍스기반의 라우터를 위에 두는 방법도 있지만 "정규식"을 통해 해결할 수 있다.

https://regexone.com/

 

RegexOne - Learn Regular Expressions - Lesson 1: An Introduction, and the ABCs

Regular expressions are extremely useful in extracting information from text such as code, log files, spreadsheets, or even documents. And while there is a lot of theory behind formal languages, the following lessons and examples will explore the more prac

regexone.com

 

728x90

+ Recent posts