반응형
🤍익스프레스로 SNS 서비스 만들기
앞서 배운 내용들을 바탕으로 로그인, 이미지 업로드, 게시글 작성, 해시태그 검색, 팔로잉 등의 기능이 있는 실제 웹 서비스 제작해보기.
- 프로젝트 구조 갖추기
1. nodebird 폴더 만든 후 package.json 생성
2. 템플릿 파일 넣을 views 폴더, 라우터 넣을 routes 폴더, 정적 파일 넣을 public 폴더 생성.
3. 프론트단 html 작성
{% extends 'layout.html' %}
{% block content %}
<div class="timeline">
<form id="join-form" action="/auth/join" method="post">
<div class="input-group">
<label for="join-email">이메일</label>
<input id="join-email" type="email" name="email"></div>
<div class="input-group">
<label for="join-nick">닉네임</label>
<input id="join-nick" type="text" name="nick"></div>
<div class="input-group">
<label for="join-password">비밀번호</label>
<input id="join-password" type="password" name="password">
</div>
<button id="join-btn" type="submit" class="btn">회원가입</button>
</form>
</div>
{% endblock %}
{% block script %}
<script>
window.onload = () => {
if (new URL(location.href).searchParams.get('error')) {
alert('이미 존재하는 이메일입니다.');
}
};
</script>
{% endblock %}
{% extends 'layout.html' %}
{% block content %}
<div class="timeline">
{% if user %}
<div>
<form id="twit-form" action="/post" method="post" enctype="multipart/form-data">
<div class="input-group">
<textarea id="twit" name="content" maxlength="140"></textarea>
</div>
<div class="img-preview">
<img id="img-preview" src="" style="display: none;" width="250" alt="미리보기">
<input id="img-url" type="hidden" name="url">
</div>
<div>
<label id="img-label" for="img">사진 업로드</label>
<input id="img" type="file" accept="image/*">
<button id="twit-btn" type="submit" class="btn">짹짹</button>
</div>
</form>
</div>
{% endif %}
<div class="twits">
<form id="hashtag-form" action="/hashtag">
<input type="text" name="hashtag" placeholder="태그 검색">
<button class="btn">검색</button>
</form>
{% for twit in twits %}
<div class="twit">
<input type="hidden" value="{{twit.User.id}}" class="twit-user-id">
<input type="hidden" value="{{twit.id}}" class="twit-id">
<div class="twit-author">{{twit.User.nick}}</div>
{% if not followerIdList.includes(twit.User.id) and twit.User.id !== user.id %}
<button class="twit-follow">팔로우하기</button>
{% endif %}
<div class="twit-content">{{twit.content}}</div>
{% if twit.img %}
<div class="twit-img"><img src="{{twit.img}}" alt="섬네일"></div>
{% endif %}
</div>
{% endfor %}
</div>
</div>
{% endblock %}
{% block script %}
<script>
if (document.getElementById('img')) {
document.getElementById('img').addEventListener('change', function(e) {
const formData = new FormData();
console.log(this, this.files);
formData.append('img', this.files[0]);
axios.post('/post/img', formData)
.then((res) => {
document.getElementById('img-url').value = res.data.url;
document.getElementById('img-preview').src = res.data.url;
document.getElementById('img-preview').style.display = 'inline';
})
.catch((err) => {
console.error(err);
});
});
}
document.querySelectorAll('.twit-follow').forEach(function(tag) {
tag.addEventListener('click', function() {
const myId = document.querySelector('#my-id');
if (myId) {
const userId = tag.parentNode.querySelector('.twit-user-id').value;
if (userId !== myId.value) {
if (confirm('팔로잉하시겠습니까?')) {
axios.post(`/user/${userId}/follow`)
.then(() => {
location.reload();
})
.catch((err) => {
console.error(err);
});
}
}
}
});
});
</script>
{% endblock %}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>{{title}}</title>
<meta name="viewport" content="width=device-width, user-scalable=no">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<link rel="stylesheet" href="/main.css">
</head>
<body>
<div class="container">
<div class="profile-wrap">
<div class="profile">
{% if user and user.id %}
<div class="user-name">{{'안녕하세요! ' + user.nick + '님'}}</div>
<div class="half">
<div>팔로잉</div>
<div class="count following-count">{{followingCount}}</div>
</div>
<div class="half">
<div>팔로워</div>
<div class="count follower-count">{{followerCount}}</div>
</div>
<input id="my-id" type="hidden" value="{{user.id}}">
<a id="my-profile" href="/profile" class="btn">내 프로필</a>
<a id="logout" href="/auth/logout" class="btn">로그아웃</a>
{% else %}
<form id="login-form" action="/auth/login" method="post">
<div class="input-group">
<label for="email">이메일</label>
<input id="email" type="email" name="email" required autofocus>
</div>
<div class="input-group">
<label for="password">비밀번호</label>
<input id="password" type="password" name="password" required>
</div>
<a id="join" href="/join" class="btn">회원가입</a>
<button id="login" type="submit" class="btn">로그인</button>
<a id="kakao" href="/auth/kakao" class="btn">카카오톡</a>
</form>
{% endif %}
</div>
<footer>
Made by
<a href="https://mycodearchive.tistory.com/" target="_blank">CodeArchive</a>
</footer>
</div>
{% block content %}
{% endblock %}
</div>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
window.onload = () => {
if (new URL(location.href).searchParams.get('loginError')) {
alert(new URL(location.href).searchParams.get('loginError'));
}
};
</script>
{% block script %}
{% endblock %}
</body>
</html>
{% extends 'layout.html' %}
{% block content %}
<div class="timeline">
<div class="followings half">
<h2>팔로잉 목록</h2>
{% if user.Followings %}
{% for following in user.Followings %}
<div>{{following.nick}}</div>
{% endfor %}
{% endif %}
</div>
<div class="followers half">
<h2>팔로워 목록</h2>
{% if user.Followers %}
{% for follower in user.Followers %}
<div>{{follower.nick}}</div>
{% endfor %}
{% endif %}
</div>
</div>
{% endblock %}
- 실행 화면
4. 데이터베이스 세팅
모델 코드는 생략
데이터베이스 & 테이블 생성 완료
반응형
'💻 my code archive > 📗Node.js' 카테고리의 다른 글
노드 공부 기록(12) - 실시간 경매 시스템 만들기, 웹 소켓, SocketIO (0) | 2022.05.11 |
---|---|
노드 공부 기록(11) - Passport 모듈 + 카카오 로그인 구현하기 (0) | 2022.04.28 |
노드 공부 기록(9) - 몽고디비 설치, 개발 환경 세팅하기 (0) | 2022.04.27 |
노드 공부 기록(8) - 시퀄라이즈 사용하기 (0) | 2022.04.27 |
노드 공부 기록(7) - Node.js 자주 사용하는 미들웨어 (0) | 2022.04.22 |