반응형

이번에는 Express.js를 이용해서 Backend를 구성해보도록 하겠습니다. Backend 서버는 Express지만,
그 외에도 추가로 MongoDB에 적당한 클러스터 생성이 필요합니다. 그리고 Kakao 로그인 연동을 위해 Kakao Developers
사이트에서 앱 생성이 필요합니다. 이부분은 많은 지문을 필요로 하는 부분이라 여기서 다루지는 않겠습니다.
 
1. 환경파일(.env) 생성
Backend 프로젝트 폴더에 .env 파일을 생성해서 비밀 환경정보 등을 저장합니다.

PORT=3000
NODE_ENV='development'
MONGO_URI='mongodb+srv://몽고디비ID:몽고디비PW@cluster0.juisc.mongodb.net/test'
KAKAO_ID=00sdfsdf6432sdfa792sdfssdfsdf28이거내가고친거임
KAKAO_REDIRECT_URI=http://localhost:3000/auth/kakao/callback
SECRET=mysecretkeytest

 
2. 패키지 설치
필요한 패키지들을 설치합니다.  설치할 땐 "npm i 패키지이름", 개발환경 의존성은 "npm i -D 패키지이름"

"dependencies": {
    "body-parser": "^1.20.2",
    "cookie-parser": "^1.4.6",
    "cors": "^2.8.5",
    "dotenv": "^16.0.3",
    "express": "^4.18.2",
    "express-session": "^1.17.3",
    "mongoose": "^7.0.4",
    "passport": "^0.6.0",
    "passport-local": "^1.0.0",
    "passport-local-mongoose": "^8.0.0",
    "pm2": "^5.3.0"
  },
  "devDependencies": {
    "nodemon": "^2.0.22"
  }

 
3. index.js
cors가 조금 문제스럽습니다. 아직도 정확히 작동하는 법은 몰라서 이것저것 남겨놓습니다.
라우팅은 포스팅 관련된 postRoute와 사용자관리 관련된 userRoute 두가지로 구성했습니다. 

import express from 'express';
import session from 'express-session';
import cookieParser from 'cookie-parser';
import bodyParser from 'body-parser';
import dotenv from 'dotenv';
import mongoose from 'mongoose';
import postRoute from './routes/postRoute.js';
import userRoute from './routes/userRoute.js';
import cors from 'cors';
import path from 'path';

dotenv.config();

const app = express();
const __dirname = path.resolve();
const { PORT, MONGO_URI, SECRET } = process.env;

app.set('port', PORT || 3000);

mongoose.connect(MONGO_URI)
    .then(() => {
        console.log('Connected to MongoDB');
    })
    .catch(e => {
        console.error(e);
    });

app.use(express.static('public'));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(cookieParser(SECRET));
app.use(session({
    resave: false,
    saveUninitialized: false,
    secret: SECRET,
    cookie: {
        httpOnly: true,
        secure: false
    }
}));
// const options = {
//     origin: "http://localhost:4000", // 접근 권한을 부여하는 도메인
//     credentials: true, // 응답 헤더에 Access-Control-Allow-Credentials 추가
//     optionsSuccessStatus: 200, // 응답 상태 200으로 설정
//   };
  
// app.use(cors(options));
app.use(cors());

app.all('/*', function(req, res, next){
    res.header("Access-Control-Allow-Origin", "*");
    res.header("Access-Control-Allow-Headers", "X-Requested-With");
    next();
})
app.use('/posts',postRoute);
app.use('/auth',userRoute);
var router = express.Router();
app.get('/', function(req,res,next){
    res.sendFile(path.join(__dirname, './public','index.html'))
})

app.listen(app.get('port'), ()=> {
  console.log(`${app.get('port')}번 포트에서 서버 실행 중..`);
});

 
4. models/Post.js
포스트 모델을 구성합니다. 

import mongoose from "mongoose";

let PostSchema = new mongoose.Schema({
  title: String,
  body: String,
  publishedDate: Date,  
  creator: {
      _id:{
          type: mongoose.Schema.Types.ObjectId,
          ref: "User"
      },
      username: String,   
      password:String,
      email: String   
  },  
});

let Post = mongoose.model("Post", PostSchema);
export default Post;

 
5. models/User.js
사용자 모델을 생성합니다.

import mongoose from 'mongoose';
import passportLocalMongoose from 'passport-local-mongoose'


let UserSchema = new mongoose.Schema({
    username: String,
    password:String,
    email: String
});

UserSchema.plugin(passportLocalMongoose);
const User = mongoose.model("User", UserSchema);
export default User;

 
6. routes/postRoute.js

import express from 'express';
import Post from '../models/Post.js';
import User from '../models/User.js';
import bodyParser from 'body-parser'

const router = express.Router();
router.use(express.json());

// 전체 리스트 읽기
router.get('/', (req, res) => {
  Post.find()
    .then((post) =>{
      res.send({post});
    })  
});

// 신규 포스트 생성
router.post('/', async (req, res) => {  
  const { title, body } = req.body;  
  // const creator = req.body.email;
  console.log(req.body);
  const post = new Post({
    title, 
    body, 
    creator: req.body.creator,
  });
  try{
    await post.save();
    // console.log(post);    
    res.body = post;
    res.send(post);
  } catch (e) {
    console.error(e);
  }

});

// 특정 포스트 조회
router.get('/:id', async (req, res) => {
  const { id } = req.params;
  console.log(id);
  try {
    const post = await Post.findById(id).exec();
    if(!post){
      res.sendStatus(404);
      return;
    }
    res.body = post;
    res.send(post);
  } catch (e) {
    console.error(e);
  }
});

// 특정 포스트 업데이트
router.patch('/:id', async (req, res) => {
  const { id } = req.params;
  // const { title, body } = req.body;
  try{
    const post = await Post.findByIdAndUpdate(id, req.body, {new:true,}).exec();
    if(!post){
      console.sendStatus(404);
      return;
    }
    res.body = post;
    res.send(post);
  } catch(e){
    console.error(e);
  }

});

// 특정 포스트 삭제
router.delete('/:id', async (req, res) => {
  const { id } = req.params;
  // console.log(req.params);
  try{
    await Post.findByIdAndRemove(id).exec();
    res.sendStatus(204);
  } catch(e){
    console.error(e);
  }
});

export default router;

 
7. routes/userRoute.js
유저 인증은 passport-local 라이브러리를 이용한 방식으로 진행합니다. Front에서 Kakao로 인증하고, 사용자 정보를 Backend로 넘겨주면 처리합니다. 이때 URL은 '/register' 만 사용하는데, 등록된 사용자면 login을 시도하고 등록 안된 사용자면 신규로 user테이블에 생성합니다. 즉 무조건 가입/로그인 되는 방식입니다.

import express from 'express';
import User from '../models/User.js';
import passport from 'passport';
import Localstrategy from 'passport-local';

const router = express.Router();

passport.use(new Localstrategy(User.authenticate()));
passport.serializeUser(User.serializeUser());
passport.deserializeUser(User.deserializeUser());

router.use(passport.initialize());
router.use(passport.session());

const isLoggedIn = (req, res, next) => {
    if (req.isAuthenticated()) {
      return next();
    }
    res.redirect('/login');
  };

router.post('/register', async (req, res, next) => {
  const { username, email, password } = req.body;

  try{
    const existingUser = await User.findOne({ email });
    // console.log(existingUser);  //DB에서 존재하는 emial인지 확인
    if (existingUser) {
        // User already exists, redirect to login page or show error message        
        passport.authenticate('local')(req, res, () => {
            // Authentication successful
            // console.log(req.user);
            console.log("Login success!");
            return res.send(req.user);
        });
    } else {

        const newUser = new User({ username, email });
        await User.register(newUser,password); // 패스워드만 따로 보내야 해싱처리 등이 적용됨 

        passport.authenticate("local")(req, res, () => {
            // console.log(req.user);
            console.log("Success! You are registered and logged in!");
            return res.send(req.user);
        });
    }
  } catch (err) {
    return next(err);
  }  
});

router.get('/protected', isLoggedIn, (req, res) => {
    res.send('Protected content');  
});

router.get("/logout", (req, res) => {
    req.logout();
    res.redirect("back");
});



export default router;

 
passport-kakao 라이브러리도 있는데, backend에서 구현하면 계속 cors문제를 해결 못했습니다. 그래서 front에서 인증하고, 사용자 정보를 backend로 넘기는 방식으로 구현함..ㅠㅠ

반응형
반응형

Kakao 로그인을 프론트에서 작업하고, 사용자 정보 중 이름, 메일주소 등 몇가지만 백엔드에 등록하는 방식으로 진행하겠습니다. 이를 위해서는 우선 main.js에 아래의 코드를 추가해야 합니다.

 

1. main.js

import { createApp } from 'vue'
import App from './App.vue'
import { store } from './store/store.js'
import router from './router'

createApp(App)
.use(router)
.use(store)
.mount('#app')

// 아래 추가
window.Kakao.init('ce8a7d7f75e0322350135c938a5f7182');

 

2. components/KakaoLogin.vue 신규 생성

<template>
	<div class="kakaobox">
		<a id="custom-login-btn" @click="kakaoLogin()">
		<!-- <a id="custom-login-btn" @click="kakaoLogin()"> -->
			<img src="//k.kakaocdn.net/14/dn/btqCn0WEmI3/nijroPfbpCa4at5EIsjyf0/o.jpg" width="222" />
		</a>
	</div>
</template>

<script>
import axios from 'axios';

export default{
	
methods: {	
	kakaoLogin(){
		window.Kakao.Auth.login({
			scope: 'profile_nickname,profile_image, account_email',
			success:() =>{
				this.getKakaoAccount();					
			},
			fail: error => {
				console.log(error);
			},
		});
	},
	getKakaoAccount(){
		window.Kakao.API.request({
			url:'/v2/user/me',				
			success : async res => {
				const kakao_account = res.kakao_account;
				const data={
					username: kakao_account.profile.nickname,
					email : kakao_account.email,
					password: "1234"
				}
				await axios.post('/auth/register', data);
				alert("로그인 성공!");
				// console.log(result);

				this.$store.dispatch('login', data);
				this.$router.push('/')
			},
			fail: error => {
				console.log(error);
			}
		})
	}
}

}
</script>
<style scoped>
.kakaobox{
	text-align: center;
}
</style>

 

3. store/store.js 수정

import { createStore } from 'vuex';
import axios from 'axios';


axios.defaults.baseURL = 'http://localhost:3000';
axios.defaults.headers.post['Content-Type']='application/json;charset=utf-8';
axios.defaults.headers.post['Access-Control-Allow-Origin']='*';


export const store = createStore({
// export default createStore({
  state:{
    user:{
      // {username: '홍길동 email: 'abcd@abcd.com', password: '1234'}
    },
    posts: [
      // {_id: '1', title: 'apple', body: 'Im apple'},
      // {_id: '2', title: 'apple', body: 'Im apple'},
      // {_id: '3', title: 'apple', body: 'Im apple'}
    ]
  },
  mutations:{
    LOGIN(state, payload){
      state.user = payload;
    },
    updateState(state, payload) {      
      // read, create
      Object.keys(payload).forEach((key) => {
        state[key] = payload[key];        
      });
    },
    DELETEPOST(state, post_id){            
      const delete_index = state.posts.findIndex(p=> p._id === post_id);      // 삭제할 포스트의 index 찾기
      state.posts.splice(delete_index,1);      
    },
    UPDATEPOST(state,updatePost){      
      const update_store_item = state.posts.find(p => p._id === updatePost._id);      
      // console.log(updatePost)
      update_store_item.title = updatePost.title;
      update_store_item.body = updatePost.body;
    }
  },
  actions:{   
    addPost(context, newPost){      
      const id= context.state.posts.length+1;      
      const post={
        _id: id,
        title: newPost.title,
        body: newPost.body,
        creator: newPost.title
      }
      context.commit("updateState", {
        posts: [...context.state.posts, post],
      })              
    },
    deletePost(context, post_id){
      context.commit("DELETEPOST", post_id);
    },
    updatePost(context, updatePost){
      context.commit("UPDATEPOST", updatePost)
    },
    login(context, name){     
      context.commit('LOGIN', name);
    }
  }
});

 

4. views/HomeView.vue 파일 수정

<template>  
  <div class="logincheck">
    <div v-if="isloggedin">안녕하세요, {{ user.username }}님</div>    
    <router-link to="/kakaologin" v-else>Kakao Login</router-link>
  </div>
  <h1 class="appname">Real Estate Groth Status</h1>  

  <AddPost />
  <ListofPost />
</template>

<script>
import ListofPost from '../components/ListofPost.vue'
import AddPost from '../components/AddPost.vue'

export default {
  name: 'HomeView',
  components: {
    ListofPost,
    AddPost,
  },
  computed:{
    user(){
      return this.$store.state.user
    },
    isloggedin(){
      return this.$store.state.user.username? true : false;
    }
  }
}
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;  
  color: #2c3e50;
  margin-top: 60px;  
}
.appname {
  color:#373738;
  text-align: center;
}
.addbutton{
  margin: auto;
  display:flex;
  align-items:center;
  justify-content: center;
  width:50px;
  height:50px;  
  border-radius: 50%;
  background-color:blue ; 
  font-size: 36px;
  font-weight: bold;
  color:beige;
}
.logincheck{  
  text-align: right;  
  margin-right: 15px;
}
</style>

반응형
반응형

Post를 수정하는 기능을 구현하겠습니다.
 
1. components/EachofPost.vue 수정
v-if 기능을 이용하여 edit모드인지 아닌지에 따라 다른 화면을 보여주도록 구성합니다.

<template> 
  <div class="post" v-if="edit">
    <div class="post_header">
      <input class="title" type="text" name="title" v-model="title_update" />
      <button class="edit" type="submit" value="Add" @click="updateItem" >v</button>    
    </div>
    <div class="post_body">
      <textarea name="body" v-model="body_update" />   
    </div>    
  </div>   
  <div class="post" v-else>
    <div class="post_header">
      <div class="title">
        {{ id }}
        {{ titleOfPost }}                      
      </div>  
      <button class="edit" @click="editItem">v</button> 
      <button class="close" @click="removeItem">x</button>  
    </div>    
    <div class="post_body">
      {{ bodyOfPost }}
      <div class="creator">
        by {{  creator }}        
      </div>
    </div>  
    
  </div> 
</template>

<script>
export default {
  props:['id','titleOfPost', 'bodyOfPost','creator'],  
  data(){
    return{
      edit: false,
      title_update:this.titleOfPost,
      body_update:this.bodyOfPost,
    }
  } , 
  methods:{
    removeItem(){
      this.$store.dispatch("deletePost", this.id)
    },
    editItem(){
      this.edit = !this.edit;
    },
    updateItem(){
      this.$store.dispatch("updatePost", {
        _id:this.id, 
        title: this.title_update, 
        body: this.body_update
      })
      this.editItem();
    }
  }
}

</script>

<style>

.post{
  /* background-color:blanchedalmond; */
  margin: 5px auto;
  border: 1px solid grey;
  border-radius: 10px;  
  width:100%;
  min-height:100px;
}
.post .post_header{  
  display: flex;
  padding: 5px;
}
.post .post_header .title{
  display:inline-block;
  text-align: left;
  font-size: 24px;  
  height: 50px;
  width:100%;
  color: black;
  border: 0px solid;
  /* background-color:blueviolet; */
}
.post .post_header .close{
  position: relative;  
  font-size:20px;  
  width:20px;
  height:20px;  
  color:dimgrey;
  background-color: transparent;
  border-color: transparent;
}
.post .post_header .edit{
  position: relative;  
  box-sizing: border-box;
  font-size:20px;  
  width:20px;
  height:20px;  
  color:dimgrey;
  background-color: transparent;
  border-color: transparent;
}

.post .post_body{
  font-size: 16px;
  color:cadetblue;
  text-align: left;
  padding: 5px;
  border: 0px solid;
  /* background-color: orange; */
  width: 100%;
  min-height: 25px;
}
.post .post_body .creator{
  text-align: right;
  left:0;
  right: 0;
  top: 0;
  bottom: 0;
  margin-right: 10px;
  margin-bottom: 5px;
  /* right: 10px; */
}
.post .post_body textarea{
  width: 95%;
  min-height:24px;
  border: 0px;
  font-size: 16px;
  color:cadetblue;
}
</style>

 

 

2. store/store.js

import { createStore } from 'vuex';
import axios from 'axios';


axios.defaults.baseURL = 'http://localhost:3000';
axios.defaults.headers.post['Content-Type']='application/json;charset=utf-8';
axios.defaults.headers.post['Access-Control-Allow-Origin']='*';


export const store = createStore({
// export default createStore({
  state:{
    user:{
      // {username: '홍길동', email: 'abcd@abcd.com', password: '1234'}
    },
    posts: [
      // {_id: '1', title: 'apple', body: 'Im apple'},
      // {_id: '2', title: 'apple', body: 'Im apple'},
      // {_id: '3', title: 'apple', body: 'Im apple'}
    ]
  },
  mutations:{    
    updateState(state, payload) {
      // ['movies','message','loading'], 액션에서 넘어온 값(payload)를 기존 state에 할당해준다.
      // read, create
      Object.keys(payload).forEach((key) => {
        state[key] = payload[key];        
      });
    },
    DELETEPOST(state, post_id){            
      const delete_index = state.posts.findIndex(p=> p._id === post_id);      // 삭제할 포스트의 index 찾기
      state.posts.splice(delete_index,1);      
    },
    UPDATEPOST(state,updatePost){      
      const update_store_item = state.posts.find(p => p._id === updatePost._id);      
      // console.log(updatePost)
      update_store_item.title = updatePost.title;
      update_store_item.body = updatePost.body;
    }
  },
  actions:{   
    addPost(context, newPost){      
      const id= context.state.posts.length+1;      
      const post={
        _id: id,
        title: newPost.title,
        body: newPost.body,
        creator: newPost.title
      }
      context.commit("updateState", {
        posts: [...context.state.posts, post],
      })   
      // console.log(context.state.user)        
    },
    deletePost(context, post_id){
      context.commit("DELETEPOST", post_id);
    },
    updatePost(context, updatePost){
      context.commit("UPDATEPOST", updatePost)
    }
  }
});

 

 
 
 
반응형
반응형

Store를 적용해보겠습니다. 

npm i vuex axios  // axios는 나중에 backend 통신을 위해 미리 설치

 
1. store/store.js
기본 post 리스트를 생성하고, 포스트 추가/삭제기능을 구현해놓는다. 기능은 추후 Backend와의 연동을 위해 actions에 구현해놓습니다.

import { createStore } from 'vuex';
// import axios from 'axios';


export const store = createStore({
// export default createStore({
  state:{
    posts: [
      {_id: '1', title: 'apple', body: 'Im apple', creator: 'apple'},
      {_id: '2', title: 'banana', body: 'Im banana', creator: 'banana'},
      {_id: '3', title: 'orange', body: 'Im orange', creator: 'orange'}
    ]
  },
  mutations:{
    updateState(state, payload) {
      Object.keys(payload).forEach((key) => {
        state[key] = payload[key];        
      });
    },
    DELETEPOST(state, post_id){            
      const delete_index = state.posts.findIndex(p=> p._id === post_id);      // 삭제할 포스트의 index 찾기
      state.posts.splice(delete_index,1);      
    },
  },
  actions:{     
    addPost(context, newPost){      
      const id= context.state.posts.length+1;      
      const post={
        _id: id,
        title: newPost.title,
        body: newPost.body,
        creator: newPost.title
      }
      context.commit("updateState", {
        posts: [...context.state.posts, post],
      })   
      // console.log(context.state.user)        
    },
    deletePost(context, post_id){
      context.commit("DELETEPOST", post_id);
    }       
  }
});

 
2. main.js 수정

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import { store } from './store/store.js'  // 추가

createApp(App)
.use(router)
.use(store)		// 추가
.mount('#app')

 
3. components/ListofPost.vue수정

<template>
  <ul>
    <li v-for="post in posts.slice().reverse()" :key=post>
      <EachofPost 
        :id=post._id
        :titleOfPost=post.title 
        :bodyOfPost=post.body 
        :creator=post.creator />
    </li>
  </ul>
</template>
<script>
import EachofPost from './EachofPost.vue'

export default {
  name: 'ListofPost',
  components: {
    EachofPost,
  },
  
// 아래 추가 부분
  computed:{
    posts(){
      return this.$store.state.posts;
    }
  }
// 여기까지 추가, 아래는 주석 or 삭제

//   data(){
//     return {
//       posts: [
//         {
//           _id:1,
//           title: 'myPost',
//           body: 'My Post 1',
//           creator: 'apple'
//         },
//         {
//           _id:2,
//           title: 'myPost2',
//           body: 'My Post 2',
//           creator: 'banana'
//         }
//       ]
//     }
//   }
// }
  
}
</script>

<style>
ul{
  padding-left:0;
}
li{
  list-style: none;
}
</style>

 
4. components/AddPost.vue
포스트를 추가하는 기능을 구현합니다. 편의상 작성자는 title과 동일하게 가져가겠습니다.

<template>  
  <div class="new_post">
    <div class="new_post_header">
      <input type="text" name="title" v-model="title" class="title"/>
      <input type="submit" value="Add" @click="addPost" class="submit">
    </div>
    <div class="new_post_body">
      <textarea class="contents" name="body" v-model="body" />
    </div>  
  </div>  
</template>

<script>
export default {  
  data(){
    return {
      title:'제목',
      body:'내용',
    };
  },
  methods:{
    addPost(){
      this.$store.dispatch('addPost',{
        title: this.title,
        body: this.body,
        creator: this.title,
      })
    }
  }
}
</script>

<style>

.new_post{
background-color:#E0EDFB;
/* margin: 5px auto; */
border: 1px solid grey;
border-radius: 10px;  
box-sizing: content-box;
width:100%;
}
.new_post .new_post_header{    
display: flex;
padding: 5px;
}
.new_post .new_post_header .title{
display:inline-block;
text-align: left;
font-size: 24px;  
height: 50px;
width:100%;
color: black;
border: 0px solid black;
background-color:transparent;
}
.new_post .new_post_header .submit{
position: relative;  
font-size:20px;  
color:dimgrey;
background-color: transparent;
border-color: transparent;
}

.new_post .new_post_body{
position: relative;  
font-size: 16px;
color:cadetblue;
text-align: left;

/* padding: 5px; */
}

.new_post .new_post_body .contents{  
position: relative;    
border: 0px;    
left:0;
right:0;
margin: 5px;
width:99%;  
box-sizing: border-box;
background-color:transparent;
}
</style>

 
5. components/EachofPost.vue
포스트를 삭제하는 기능을 구현합니다.

<template>  
  <div class="post">
    <div class="post_header">
      <div class="title">
        {{ id }}
        {{ titleOfPost }}                      
      </div>  
      <button class="close" @click="removeItem">x</button>  
    </div>    
    <div class="post_body">
      {{ bodyOfPost }}
      <div class="creator">
        by {{  creator }}        
      </div>
    </div>  
    
  </div> 
</template>

<script>
export default {
  props:['id','titleOfPost', 'bodyOfPost','creator'],   
  // 아래 추가
  methods:{
    removeItem(){
      this.$store.dispatch("deletePost", this.id)
    }
  }
  // 여기까지 추가부분
}

</script>

<style>

.post{
  /* background-color:blanchedalmond; */
  margin: 5px auto;
  border: 1px solid grey;
  border-radius: 10px;  
  width:100%;
  min-height:100px;
}
.post .post_header{  
  display: flex;
  padding: 5px;
}
.post .post_header .title{
  display:inline-block;
  text-align: left;
  font-size: 24px;  
  height: 50px;
  width:100%;
  color: black;
  border: 0px solid;
  /* background-color:blueviolet; */
}
.post .post_header .close{
  position: relative;  
  font-size:20px;  
  width:20px;
  height:20px;  
  color:dimgrey;
  background-color: transparent;
  border-color: transparent;
}
.post .post_header .edit{
  position: relative;  
  box-sizing: border-box;
  font-size:20px;  
  width:20px;
  height:20px;  
  color:dimgrey;
  background-color: transparent;
  border-color: transparent;
}

.post .post_body{
  font-size: 16px;
  color:cadetblue;
  text-align: left;
  padding: 5px;
  border: 0px solid;
  /* background-color: orange; */
  width: 100%;
  min-height: 25px;
}
.post .post_body .creator{
  text-align: right;
  left:0;
  right: 0;
  top: 0;
  bottom: 0;
  margin-right: 10px;
  margin-bottom: 5px;
  /* right: 10px; */
}
.post .post_body textarea{
  width: 95%;
  min-height:24px;
  border: 0px;
  font-size: 16px;
  color:cadetblue;
}
</style>
반응형
반응형

1. router/index.js 수정

불필요한 라우팅을 삭제합니다.

import { createRouter, createWebHistory } from 'vue-router';
import Home from '../views/HomeView.vue';


const routes = [
  {
    path:'/',
    name: 'Home',
    component: Home
  }
]

const router = createRouter({
  history: createWebHistory(process.env.BASE_URL),
  routes
})

export default router;

 

2. 불필요한 view도 삭제합니다.

views/AboutView.vue --삭제

 

3. App.vue 수정

<template>    
  <router-view />
</template>

<script>
 
</script>

<style>
 
</style>

 

4. views/HomeView.vue 수정

Todo 대신 Post라는 이름으로 진행합니다. 홈뷰에서는 전체 Post를 가져오는 컴포넌트와, 신규 Post를 작성하는 컴포넌트를 둘 예정입니다.

<template> 
  <h1 class="appname">My Todo App</h1>  
  <AddPost />
  <ListofPost />
</template>

<script>
import ListofPost from '../components/ListofPost.vue'
import AddPost from '../components/AddPost.vue'

export default {
  name: 'HomeView',
  components: {
    ListofPost,
    AddPost,
  }
}
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;  
  color: #2c3e50;
  margin-top: 60px;  
}
.appname {
  color:#373738;
  text-align: center;
}
.addbutton{
  margin: auto;
  display:flex;
  align-items:center;
  justify-content: center;
  width:50px;
  height:50px;  
  border-radius: 50%;
  background-color:blue ; 
  font-size: 36px;
  font-weight: bold;
  color:beige;
}
.logincheck{  
  text-align: right;  
  margin-right: 15px;
}
</style>

 

5. components/ListofPost.vue

data로는 개별 포스트의 id, 타이틀, 내용, 작성자 정보를 갖고 있습니다. (나중에 store에서 가져올 예정)

각각의 포스트를 돌면서 EachofPost 컴포넌트에 값을 넣어줍니다.

각각의 EachofPost에는 해당 값이 Props로 전달됩니다.

<template>
  <ul>
    <li v-for="post in posts.slice().reverse()" :key=post>
      <EachofPost 
        :id=post._id
        :titleOfPost=post.title 
        :bodyOfPost=post.body 
        :creator=post.creator />
    </li>
  </ul>
</template>
<script>
import EachofPost from './EachofPost.vue'

export default {
  name: 'ListofPost',
  components: {
    EachofPost,
  },
  data(){
    return {
      posts: [
        {
          _id:1,
          title: 'myPost',
          body: 'My Post 1',
          creator: 'apple'
        },
        {
          _id:2,
          title: 'myPost2',
          body: 'My Post 2',
          creator: 'banana'
        }
      ]
    }
  }
}
</script>

<style>
ul{
  padding-left:0;
}
li{
  list-style: none;
}
</style>

 

6. components/EachofPost.vue

Props로 받아오는 속성들을 정의해줍니다. 그리고 화면에 출력합니다.

<template>  
  <div class="post">
    <div class="post_header">
      <div class="title">
        {{ id }}
        {{ titleOfPost }}                      
      </div>  
    </div>    
    <div class="post_body">
      {{ bodyOfPost }}
      <div class="creator">
        by {{  creator }}        
      </div>
    </div>  
    
  </div> 
</template>

<script>
export default {
  props:['id','titleOfPost', 'bodyOfPost','creator'],    
}
</script>

<style>

.post{
  /* background-color:blanchedalmond; */
  margin: 5px auto;
  border: 1px solid grey;
  border-radius: 10px;  
  width:100%;
  min-height:100px;
}
.post .post_header{  
  display: flex;
  padding: 5px;
}
.post .post_header .title{
  display:inline-block;
  text-align: left;
  font-size: 24px;  
  height: 50px;
  width:100%;
  color: black;
  border: 0px solid;
  /* background-color:blueviolet; */
}
.post .post_header .close{
  position: relative;  
  font-size:20px;  
  width:20px;
  height:20px;  
  color:dimgrey;
  background-color: transparent;
  border-color: transparent;
}
.post .post_header .edit{
  position: relative;  
  box-sizing: border-box;
  font-size:20px;  
  width:20px;
  height:20px;  
  color:dimgrey;
  background-color: transparent;
  border-color: transparent;
}

.post .post_body{
  font-size: 16px;
  color:cadetblue;
  text-align: left;
  padding: 5px;
  border: 0px solid;
  /* background-color: orange; */
  width: 100%;
  min-height: 25px;
}
.post .post_body .creator{
  text-align: right;
  left:0;
  right: 0;
  top: 0;
  bottom: 0;
  margin-right: 10px;
  margin-bottom: 5px;
  /* right: 10px; */
}
.post .post_body textarea{
  width: 95%;
  min-height:24px;
  border: 0px;
  font-size: 16px;
  color:cadetblue;
}
</style>

 

7. components/AddPost.vue

포스트 추가하는 부분을 모양만 만들어놓습니다.

<template>  
  <div class="new_post">
    <div class="new_post_header">
      <input type="text" name="title" v-model="title" class="title"/>
      <input type="submit" value="Add" class="submit">
    </div>
    <div class="new_post_body">
      <textarea class="contents" name="body" v-model="body" />
    </div>  
  </div>  
</template>

<script>
export default {  
data(){
  return {
    title:'제목',
    body:'내용',
  };
}
}
</script>

<style>

.new_post{
background-color:#E0EDFB;
/* margin: 5px auto; */
border: 1px solid grey;
border-radius: 10px;  
box-sizing: content-box;
width:100%;
}
.new_post .new_post_header{    
display: flex;
padding: 5px;
}
.new_post .new_post_header .title{
display:inline-block;
text-align: left;
font-size: 24px;  
height: 50px;
width:100%;
color: black;
border: 0px solid black;
background-color:transparent;
}
.new_post .new_post_header .submit{
position: relative;  
font-size:20px;  
color:dimgrey;
background-color: transparent;
border-color: transparent;
}

.new_post .new_post_body{
position: relative;  
font-size: 16px;
color:cadetblue;
text-align: left;

/* padding: 5px; */
}

.new_post .new_post_body .contents{  
position: relative;    
border: 0px;    
left:0;
right:0;
margin: 5px;
width:99%;  
box-sizing: border-box;
background-color:transparent;
}
</style>

 

<결과>

 

반응형
반응형

1. Vue 프로젝트 생성

vue_front 폴더로 이동하여 vue_프로젝트를 생성합니다.

cd vue_front

npm i -g @vue/cli // vuejs 프로젝트가 처음일 경우 글로벌로 vue cli를 설치해줍니다.

vue create . // 현재의 폴더에 vue 프로젝트를 생성합니다.

 

2. Vue-router 설치

vue-router를 설치하면 프로젝트 구조가 조금 달라집니다. 따라서 vue-router를 먼저 설치하고 진행합니다.

vue add router

 

3. vuex / axios 설치 및 port 설정

npm i vuex axios
npm i nodemon --save-dev

 

package.json의 scripts 부분을 수정하여 4000번 포트에서 서비스하도록 수정합니다.

 "scripts": {
    "serve": "vue-cli-service serve --port 4000",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint"
  },

 

반응형
반응형

Vue.js를 이용해 백엔드/로그인 연동까지 가능한 Todolist를 만들도록 하겠습니다.

 

폴더구조는 아래와 같이 하나의 프로젝트 폴더에 backend와 frontend를 모아놓고 시작하겠습니다.

TODO_APP
   - express_back
   - vue_front

 

 

반응형
반응형

1. Vue 프로젝트 시작

vue create . //폴더를 먼저 만들고 해당 폴더를 프로젝트 폴더로 하여 생성

 

2. Electron-Builder 설치

이거 설치하고 테스트를 해본다.

vue add electron-builder

 

3. 배포 테스트

npm run electron:build

 

4. Router 설치

 

vue add router

 

- App.vue파일이 자동으로 업데이트 됨

<template>
  <div id="nav">
    <router-link to="/">Home</router-link>
    <router-link to="/about">About</router-link>
  </div>
  <router-view>
</template>

- ./views/HomeView.vue와 /views/AboutView.vue 파일이 자동 생성됨-

- ./router/index.js 파일이 자동 생성됨

 

5. Bootstrap-vue 설치

npm install bootstrap-vue bootstrap

 Bootstrap-vue 설정 (main.js)

import BootstrapVue from 'bootstrap-vue'
import 'bootstrap/dist/css/bootstrap.min.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'

Vue.use(BootstrapVue)

 

6. 기타 라이브러리 설치

npm i axios, vuex
반응형

+ Recent posts