Watcher Habitμ κ°μΈμ μ΅κ΄μ ν¨κ³Όμ μΌλ‘ κ΄λ¦¬νκ³ , μκ·λͺ¨ κ·Έλ£Ήκ³Ό μ΅κ΄μ 곡μ νλ νλ«νΌμ λλ€. μ¬μ©μλ€μ΄ μλ‘μ μ΅κ΄μ 곡μ νλ©°, μλ‘λ₯Ό μ§μΌλ³΄λ©΄μ λκΈ°λΆμ¬λ₯Ό μ»μ μ μμ΅λλ€. μ΄λ₯Ό ν΅ν΄ μ΅κ΄μ μ§μμ±μ λμ΄κ³ , κ·Έλ£Ή λ΄μ λλ£λ€κ³Ό ν¨κ» μ±μ₯νλ κ²½νμ ν μ μμ΅λλ€.
πΉ μμ° μμ: Watcher Habit μμ° μμ μ νλΈ λ§ν¬
- π‘ νλ‘μ νΈ λκΈ°
- π§ λμ λ° λ¬Έμ ν΄κ²°
- 1. ν΄λΌμ΄μΈνΈ μν κ΄λ¦¬
- 2. μ΅κ΄ νμ€ν 리 μ μ₯μ μν ν μ΄λΈ μ€κ³
- 3. μ€μκ° μλ¦Ό
- 4. λ‘κ·ΈμΈ κ³Όμ μμμ 보μ κ³ λ €μ¬ν
- π μΌμ
- π οΈ κΈ°μ μ€ν
- π μΈν λ°©λ²
- π₯ ν λ©€λ²
- π Repository μ£Όμ
- π κΈ°ν μ¬ν
νλ‘μ νΈ κΈ°ν λ¨κ³μμ Last-Survivors 3/8 νμμ μλ‘ λ€λ₯Έ κΈ°μ μ ν₯λ―Έμ νμ΅ λͺ©νλ₯Ό κ°μ§κ³ μμμ΅λλ€.
μ§ν
- 리μ‘νΈ νμ© λ° μν κ΄λ¦¬ ν΄ λμ
- λ°μν μΉ μ± κ΅¬ν
- μ¬μ©μ μ€μ¬μ UI/UX μ€κ³
- μ΄κΈ° κΈ°ν λ¨κ³μμμ λ°©ν₯μ± λͺ νν
μν
- μ€μκ° μλ² μ²λ¦¬ κ²½ν
- μλ°μ€ν¬λ¦½νΈ μ€μ¬μ μλ² μμ λ°©μ κΉμ νꡬ
μ§μ
- μ κ· κΈ°μ κ³Ό λΌμ΄λΈλ¬λ¦¬ λμ κ²½ν
- μμ μ μΈ κΈ°μ μ€ν νμ© λ° νλ‘μ νΈ ν¨μ¨μ± ν₯μ
μνλμ νμ λ Έμ μΌλ‘ μ€μ€λ‘μ μ΅κ΄ νμ±μ κΈ°λ‘νκ³ μμλλ°, νΌμλ§μ κΈ°λ‘μ λκΈ° λΆμ¬μ νκ³κ° μμμ΅λλ€. μ΄λ¬ν λ¬Έμ μ μ ν΄κ²°νκΈ° μν΄ μ΅κ΄ κΈ°λ‘μ μλ‘ κ³΅μ νκ³ νμΈν μ μλ μκ·λͺ¨ κ·Έλ£Ήμ νμ±νλ μμ΄λμ΄κ° μ μλμμ΅λλ€. μ΄ μμ΄λμ΄λ κ·Έλμ 곡λΆν΄μ¨ 리μ‘νΈλ‘λ μΆ©λΆν ꡬν κ°λ₯νλ©΄μ, κ΄λ¦¬ν μνκ° λ§μ κ²μΌλ‘ μμλμκΈ° λλ¬Έμ μν κ΄λ¦¬ ν΄μ νμμ±μ΄ λλ€κ³ μκ°λμμ΅λλ€. λ, μ΄μ νλ‘μ νΈμλ λ€λ₯΄κ² μλ²μ ν΄λΌμ΄μΈνΈλ₯Ό λΆλ¦¬νμ¬ μμ νλ λ± λ€μν κΈ°μ μ λμ μ ν¬ν¨νκ³ μμμ΅λλ€.
λͺ¨λ νμμ κΈ°μ μ ν₯λ―Έμ μνλμ μ΅κ΄ κΈ°λ‘ μμ΄λμ΄κ° ν©μ³μ Έ, μ¬μ©μλ€μ΄ μλ‘μ μ΅κ΄μ μ§μΌλ³΄λ©° μλ‘λ₯Ό λμμ£Όλ μΉ μ ν리μΌμ΄μ 'Watcher Habit'μ΄ νμνκ² λμμ΅λλ€. μ¬μ©μλ€μκ² μλ‘μ μ΅κ΄μ λͺ¨λν°λ§νλ©° λκΈ°λ₯Ό λΆμ¬νλ μλΉμ€μ λλ€.
3μ£Ό λμ κ°λ°μ μ§ννλ©΄μ κ²ͺμλ μλ²μ ν΄λΌμ΄μΈνΈμμ λ°μν ν΅μ¬μ μΈ λ¬Έμ λ€μ μ λ¦¬ν΄ λ³΄μμ΅λλ€.
νλ‘ νΈμλ κ°λ° μ€ μν κ΄λ¦¬μ μ€μμ±μ κΉ¨λ¬μμ΅λλ€. μ¬λ¬ μ»΄ν¬λνΈμμ μνλ₯Ό 곡μ νκ³ , μλ² λ°μ΄ν°μμ λκΈ°ν λ° μν μ λ°μ΄νΈκ° νμνμ΅λλ€. νΉν, μλ¦Όμ μ‘°ννκ±°λ μ΅κ΄ μ 보λ₯Ό μ‘°ν, μμ , μΆκ°ν λλ§λ€ μ€μκ°μΌλ‘ μ μ μκ² λ³΄μ¬μ£Όλ λΆλΆμμ μν κ΄λ¦¬λ₯Ό ν΅ν΄ μ½λλ₯Ό κ°κ²°νκ² λ§λ€μ΄μΌ νμ΅λλ€.
μ»΄ν¬λνΈ λΆλ¦¬ κ³Όμ μμ props drilling νμμ΄ λ°μνμ΅λλ€. μ΄λ₯Ό ν΄κ²°νκ³ μνλ₯Ό ν¨μ¨μ μΌλ‘ κ΄λ¦¬νκΈ° μν΄ λ€μν μν κ΄λ¦¬ ν΄μ μ‘°μ¬νκ³ μ μ©ν νμκ° μμμ΅λλ€.
React λ΄μ₯ Hook μ€ useState
λ λΉκ΅μ κ°λ¨ν λ‘컬 μν κ΄λ¦¬μ μ ν©νλ―λ‘, λ‘λ© μν, λͺ¨λ¬μ μ΄λ¦Ό λ° λ«ν, κ·Έλ£Ή λͺ©λ‘μ νμ μ¬λΆμ κ°μ κ°λ¨ν UI μνλ₯Ό κ΄λ¦¬νλ λ° μ¬μ©λμμ΅λλ€.
μ μ μν κ΄λ¦¬μλ Reduxμ Redux Toolkitμ μ¬μ©νμ¬ μ΅κ΄ μ 보, μλ¦Ό μ 보 λ±μ μ μ μνλ₯Ό μ€μμμ κ΄λ¦¬νμμ΅λλ€. μ΄λ₯Ό ν΅ν΄ μ΅κ΄μ΄λ μλ¦Ό μ λ³΄κ° λ³κ²½λ λλ§λ€ μ€μκ°μΌλ‘ μνλ₯Ό μ λ°μ΄νΈνκ³ μ¬μ©μμκ² λ°μν μ μκ² λμμ΅λλ€.
μλ²μμ λ°μ΄ν° λκΈ°νλ₯Ό μν΄ react-query
λ₯Ό μ¬μ©νμμ΅λλ€. νΉν, μ’μΈ‘ μ¬μ΄λλ° μ»΄ν¬λνΈμμ κ·Έλ£Ήμ μμ±νλ©΄ μμ±λ κ·Έλ£Ήμ νμ΄μ§λ‘ μ΄λλλ©΄μ λμμ κ·Έλ£Ή 리μ€νΈμ μ€μκ°μΌλ‘ μλ‘μ΄ κ·Έλ£Ήμ μ΄λ¦μ΄ λνλμΌ νμ΅λλ€. μ΄λ₯Ό μν΄ μλμ 컀μ€ν
ν
μ ꡬννμ¬ μλ²μμ μ΅μ μ μ¬μ©μ λ°μ΄ν°λ₯Ό κ°μ Έμ€λλ‘ νμ΅λλ€. λν react-query
μ λ°μ΄ν° μΊμ± κΈ°λ₯μ νμ©νμ¬ μ±λ₯μ ν₯μμμΌ°μ΅λλ€.
react-queryλ₯Ό μ μ©ν 컀μ€ν ν μμ
μ΄ μ»€μ€ν ν μμλ react-query λΌμ΄λΈλ¬λ¦¬μ useQuery ν μ μ¬μ©νμ¬ μ μ μ‘°ν APIλ₯Ό ν΅ν΄ μ¬μ©μ λ°μ΄ν°λ₯Ό μμ²ν©λλ€. μ΄ λ, useQueryμ enabled μ΅μ μ μ¬μ©νμ¬ μ‘°κ±΄μ λ°λΌ λ°μ΄ν° μμ²μ νμ±ννκ±°λ λΉνμ±νν μ μμΌλ―λ‘ μ μ IDκ° μ ν¨ν κ²½μ°μλ§ μ¬μ©μ λ°μ΄ν°λ₯Ό μμ²νλλ‘ μ€κ³νμμ΅λλ€. useQueryλ μμ²ν λ°μ΄ν°λ₯Ό μλμΌλ‘ μΊμνλ―λ‘ κ°μ λ°μ΄ν°μ λν μ¬μμ² μ λΆνμν μλ² νΈμΆμ μ€μ¬μ€λλ€. μΆκ°λ‘ κ·Έλ£Ήμ΄ μμ±λ λλ§λ€ μ΅μ μ κ·Έλ£Ή 리μ€νΈλ₯Ό λ€μ κ°μ Έμ€κΈ° μν΄μ μ’μΈ‘ μ¬μ΄λλ°μ κ°μ ν κ·Έλ£Ή 리μ€νΈ μ»΄ν¬λνΈμμ refetchλ₯Ό μ¬μ©νμμ΅λλ€.import { useQuery } from 'react-query';
import userGetAPI from '../services/api/userGet';
export const useFetchUserData = (userId) => {
const fetchUserData = async () => {
const response = await userGetAPI(userId, 'group', true);
return response.groups.map((group) => ({
groupId: group._id,
groupName: group.groupName,
}));
};
const {
data: groupList,
refetch,
isLoading,
} = useQuery(['userData', userId], fetchUserData, {
enabled: !!userId,
});
return { groupList, refetch, isLoading };
};
Redux Toolkitκ³Ό react-queryλ₯Ό ν¨κ³Όμ μΌλ‘ μ‘°ν©νμ¬ μ¬μ©νλ €κ³ λ Έλ ₯νμ΅λλ€. κ·Έλ¦¬κ³ μ»€μ€ν ν μ λμ μΌλ‘ μ»΄ν¬λνΈ λ΄μ λ‘μ§κ³Ό μν κ΄λ¦¬ λ‘μ§μ λͺ ννκ² λΆλ¦¬ν¨μΌλ‘μ¨ νλ‘μ νΈ λ΄μ μν κ΄λ¦¬ 볡μ‘μ±μ ν¬κ² μ€μμ΅λλ€. μ΄λ¬ν μ κ·Όμ μ½λμ κ°λ μ±, μ μ§ λ³΄μμ±μ ν₯μμμΌ°μ΅λλ€. λν μ€μκ° μν μ λ°μ΄νΈμ λ°μ΄ν° μΊμ± κΈ°λ₯μ ν΅ν΄ μ¬μ©μμκ² λμ± λΉ λ₯΄λ©΄μλ λΆλλ¬μ΄ μλ΅μ μ 곡ν μ μκ² λμμΌλ―λ‘ μ¬μ©μ κ²½νμ ν₯μμμΌ°μ΅λλ€.
μ΅κ΄ 곡μ νλ«νΌμ κ°λ°νλ©΄μ λ°κ²¬ν νκ°μ§ λ¬Έμ μ μ νμ¬ λ μ§μ ν΄λΉνλ μ΅κ΄λ€λ§ μ μ μκ² λ³΄μ¬μ§κ² λμ΄ μμ΄ μ΄μ μ ννλ μ΅κ΄ κΈ°λ‘μ νμΈνκΈ° μ΄λ ΅λ€λ κ²μ΄μμ΅λλ€. μ μ κ° κΎΈμ€ν ν΄μ¨ μ΅κ΄μ νμΈνκΈ° μν΄μ μ΄μ κΈ°λ‘ μ μ₯μ μ ν¬ νλ‘μ νΈμ λ°λμ νμν κΈ°λ₯μ΄μμ΅λλ€.
λ°λΌμ μ΄λ―Έ μ§νν μ΅κ΄ λ΄μμ μ μ₯νλ ν μ΄λΈμ΄ νμνμ΅λλ€. λ€λ§ λ€λ₯Έ μμ μ μν₯μ μ£Όλ©΄ μλμκΈ°μ λ°°μΉλ₯Ό ν΅ν΄ μ²λ¦¬ν νμκ° μμμ΅λλ€. κ·Έλ¦¬κ³ μ΅κ΄μ νμ€ν 리 λ°μ΄ν°λ κ³μν΄μ μμ΄κ² λλ―λ‘ λ°μ΄ν° μ‘°ν μ μλ΅ μλ μ΅μ νμλ μ£Όμλ₯Ό κΈ°μΈμ¬μΌ νμ΅λλ€.
μ΄κΈ°μλ μ‘°νμ μ©μ΄ν¨μ μν΄ ν루μ νλ² λ μ§λ³, μ μ λ³λ‘ νλ²μ λ°μ΄ν°λ₯Ό λͺ¨μμ μ μ₯νλ λ°©μμ μκ°νμ΅λλ€.
λ¬Έμ μ |
---|
1. λ μ§λ₯Ό κΈ°μ€μΌλ‘ μ μ λ³λ‘ μ μ₯νλ©΄ μ‘°ν μ λΉ λ₯΄κ³ κ°νΈνκ² μ‘°νν μ μλ€λ μ₯μ μ΄ μμ§λ§ mongodb documentμ μ μ½μ 15mb μ΄νλ‘λ§ μ μ₯ν μ μμμ΅λλ€. κ³μ°ν΄ 보λ ν루 μ μ 1λͺ μ΄ νκ· 5κ°μ μ΅κ΄μ νλ€λ κ°μ νμ λλ΅ 3μ²λͺ λΆλ§ μ μ₯ν μ μμμ΅λλ€. μ΄λ λ§μ μ μ κ° μ¬μ©ν΄μΌνλ μ΄ν리μΌμ΄μ μ μ μν₯μ μ£ΌκΈ°μ λ°μ΄ν°λ₯Ό νλλ‘ λ¬ΆκΈ°λ μ΄λ €μ μ΅λλ€. |
2. μ΅κ΄λ€μ μ μ₯νλ €κ³ ν λ μμ§ λλμ§ μμ μ΅κ΄μ΄ μλ€λ©΄ μ΄ μ΅κ΄μ κΈ°λ‘ν΄λκΈ°κ° μ΄λ €μ μ΅λλ€. μλ² μ±λ₯μ μν₯μ μ£Όμ§ μκΈ° μν΄ μλ²½μ νλ €κ³ ν΄λ μλ²½μ μννλ μ΅κ΄μ΄ μλ κ²½μ° λ°°μΉ μκ°λ³΄λ€ λ€μ μ’ λ£λ μ°λ €κ° μμμ΅λλ€. |
ν΄κ²° λ°©λ² |
---|
κ°κ°μ μ΅κ΄λ§λ€ λ μ§μ μ μ idλ₯Ό 컬λΌμΌλ‘ κ°λ documentλ‘ μ μ₯νλ λ°©μμ μ¬μ©νμ΅λλ€. |
const mongoose = require('mongoose');
const HabitSchema = require('./Habit').schema;
const HabitHistorySchema = new mongoose.Schema(
{
date: {
type: String,
required: true,
validate: {
validator(v) {
return /^(\d{4})-(\d{2})-(\d{2})$/.test(v);
},
message: 'Invalid date format',
},
},
userId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true,
},
habitId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Habit',
required: true,
},
habitDetails: HabitSchema,
},
{
timestamps: true,
},
);
module.exports = mongoose.model('HabitHistory', HabitHistorySchema);
κ·Έ κ²°κ³Ό μ μ κ° μ΄μ μ ννλ μ΅κ΄μ κ²°κ³Όλ₯Ό 보μ¬μ£Όλ μ£Όκ° μ΅κ΄ νμ΄μ§μμ μ μ μ μ΄μ κΈ°λ‘μ κ°μ Έμ¬ μ μκ² λμμ΅λλ€. κ·Έλ¦¬κ³ mongodbμ document μ©λ μ ν μ΄κ³Ό λ¬Έμ ν΄κ²°. μ΄λ€ μκ°μ λ°°μΉλ₯Ό μνν΄λ μ μμ μΌλ‘ λ°μ΄ν°λ₯Ό μ μ₯ν μ μμμ΅λλ€. μ‘°ν μμ μ±λ₯ λ¬Έμ λ μΈλ±μ€λ‘ 쿼리 νλμ νμ¬ λ³΄μνμμ΅λλ€.
νλ‘μ νΈμ ν΅μ¬ κΈ°λ₯ μ€ νλλ μ¬μ©μκ° μ΄ν리μΌμ΄μ λ΄μμ λ°μνλ λ€μν μ΄λ²€νΈλ€μ λν΄ μ€μκ°μΌλ‘ μλ¦Όμ λ°λ κ²μ΄μμ΅λλ€. μ΄λ₯Ό ν΅ν΄ μ¬μ©μλ μλ‘μ΄ κ·Έλ£Ή μ΄λ, μ΅κ΄ νμΈ μμ², μλ¦Ό μΉμΈ μμ² λ±μ μ΄λ²€νΈλ₯Ό μ¦κ°μ μΌλ‘ μΈμ§νκ³ λ°μν μ μμ΄μΌ νμ΅λλ€.
μ€μκ° μλ¦Όμ ꡬννκΈ° μν μ¬λ¬ λ°©λ² μ€, Server-Sent Events (SSE)λ₯Ό μ νν μ΄μ λ λ€μκ³Ό κ°μ΅λλ€:
- κ°λ¨ν ꡬν: SSEλ μΉ νμ€ κΈ°μ λ‘μ ꡬνμ΄ κ°λ¨νλ©°, μΆκ°μ μΈ λΌμ΄λΈλ¬λ¦¬λ νλ μμν¬ μμ΄λ μΉ μλ²μ λΈλΌμ°μ μμ μ§μλ©λλ€.
- μλ μ¬μ°κ²°: λ§μ½ λ€νΈμν¬ λ¬Έμ λ‘ μ°κ²°μ΄ λμ΄μ‘μ λ, SSEλ μλμΌλ‘ μ¬μ°κ²°νλ κΈ°λ₯μ λ΄μ₯νκ³ μμ΄, λ³λμ μ¬μ°κ²° λ‘μ§μ ꡬνν νμκ° μμμ΅λλ€.
- λ¨λ°©ν₯ ν΅μ μΌλ‘ μΆ©λΆν ꡬν κ°λ₯νλ€κ³ νλ¨: νλ‘μ νΈμμ νμν μλ¦Όμ λ°κΈ°λ§ νλ©΄ μλ²μμ λ€λ₯Έ μμ μ ν΄μ£Όμ§ μμλ λλ κ²μΌλ‘ κΈ°ννκΈ° λλ¬Έμ, λ¨λ°©ν₯ μ μ‘ λ°©λ²μΈ SSEλ‘ μΆ©λΆν ꡬνμ΄ κ°λ₯νμ΅λλ€.
νλ‘μ νΈμμλ initEventSource
ν¨μλ₯Ό μ¬μ©νμ¬ SSE μ°κ²°μ μ΄κΈ°ννμκ³ , μ°κ²°μ΄ μ±κ³΅μ μΌλ‘ μ΄λ£¨μ΄μ§λ©΄ μλ²μμ ν΄λΌμ΄μΈνΈλ‘ μλ¦Ό λ°μ΄ν°λ₯Ό μ μ‘ν©λλ€. μ΄λ₯Ό μμ ν ν΄λΌμ΄μΈνΈλ μλ¦Ό λ©μμ§λ₯Ό νλ©΄μ νμνλ©°, μΌμ μκ° νμλ μλμΌλ‘ μλ¦Όμ νλ©΄μμ μ κ±°ν©λλ€.
SSEλ₯Ό λμ ν¨μΌλ‘μ¨ μ¬μ©μλ μ΄ν리μΌμ΄μ μμ λ°μνλ μ€μν μ΄λ²€νΈλ€μ μ€μκ°μΌλ‘ νμΈν μ μκ² λμμ΅λλ€. μ΄λ₯Ό ν΅ν΄ μ¬μ©μλ νμν μ 보λ μμ²μ μ¦μ νμΈνκ³ μ²λ¦¬ν μ μκ² λμ΄ μ¬μ©μ κ²½νμ ν₯μμ μ΄λ£° μ μμμ΅λλ€.
λν, μλ²μμ ν΄λΌμ΄μΈνΈλ‘μ μ€μκ° ν΅μ ꡬνμΌλ‘ μΈν΄ νλ‘μ νΈμ λμ μΈ μνΈμμ©μ΄ νλλμμ΅λλ€.
μ°λ¦¬ νλ‘μ νΈμμλ SSEλ₯Ό μ¬μ©νμ¬ μ€μκ° μλ¦Ό κΈ°λ₯μ ꡬννμμ΅λλ€. SSEμλ λ€μν μ₯μ μ΄ μμ§λ§, λμμ μΉμμΌμ λΉν΄ λͺ κ°μ§ νκ³μ μ΄ μμ΅λλ€.
SSEμ νκ³
-
λ¨λ°©ν₯ ν΅μ : SSEλ μλ²μμ ν΄λΌμ΄μΈνΈλ‘μ λ¨λ°©ν₯ ν΅μ λ§ μ§μν©λλ€. μ΄λ‘ μΈν΄ ν΄λΌμ΄μΈνΈμμμ λμμ΄ μλ²μ μ€μκ°μΌλ‘ λ°μλκΈ° μ΄λ ΅μ΅λλ€. μλ₯Ό λ€λ©΄, μλ¦Όμμμ νΉμ μ‘μ μ μλ²μ μ¦κ°μ μΌλ‘ λ°μνλ κ²μ΄ μ΄λ ΅μ΅λλ€.
-
μ§μμ μ°κ²° μ μ§: λ§μ μ¬μ©μκ° λμμ SSE μ°κ²°μ μ μ§ν κ²½μ° μλ²μ λΆνκ° μ¦κ°νκ² λ©λλ€. νμ¬ μ°κ²°λ μ μ idλ₯Ό λͺ¨λ connectionμΌλ‘ λͺ¨μλκ³ μλλ° μ΄ ν¬κΈ°κ° 컀μ§λ©΄ μλ² λ΄μμ λ©λͺ¨λ¦¬λ₯Ό κ³μν΄μ λ§μ΄ μ°¨μ§ν μ μκΈ° λλ¬Έμ λΆνκ° μ¦κ°νλ μμΈμ΄ λ©λλ€.
μΉμμΌμ μ₯μ λ° νλ‘μ νΈμμμ μμ¬μ
-
μλ°©ν₯ ν΅μ : μΉμμΌμ μλ²μ ν΄λΌμ΄μΈνΈ κ°μ μλ°©ν₯ ν΅μ μ μ§μν©λλ€. μ΄λ‘ μΈν΄ μλ¦Όμ°½μ μ€μκ° κ°±μ κ³Ό κ°μ κΈ°λ₯μ λ ν¨μ¨μ μΌλ‘ ꡬνν μ μμ΅λλ€.
-
μλ¦Όμ°½ μ€μκ° κ°±μ : νλ‘μ νΈμμλ 리μ‘νΈ μΏΌλ¦¬λ₯Ό μ¬μ©νμ¬ 10μ΄λ§λ€ μλ¦Όμ μ‘°ννλ λ°©μμ μ±ννμμ΅λλ€. μΉμμΌμ μ¬μ©νλ€λ©΄, μλ²μμ μ κ· μλ¦Ό λ°μ μ ν΄λΌμ΄μΈνΈμ μ¦μ μ μ‘νμ¬ μλ¦Όμ°½μ μ€μκ°μΌλ‘ κ°±μ νλ κ²μ΄ κ°λ₯νμ κ²μ λλ€.
-
ν¨μ¨μ± λ° λ€νΈμν¬ μ΅μ ν: μΉμμΌμ λ°μ΄ν°λ₯Ό μ μ‘ν λ ν€λκ° λΆνμνμ¬, λ€νΈμν¬μ μ€λ²ν€λκ° μ μ΅λλ€. λ°λΌμ, μ€μ λ‘ μλ¦Όμ΄ λ°μνμ λλ§ λ°μ΄ν°λ₯Ό μ μ‘νκ² λμ΄ λ€νΈμν¬ λ¦¬μμ€λ₯Ό λ³΄λ€ ν¨μ¨μ μΌλ‘ μ¬μ©ν μ μμ΅λλ€.
-
νμ₯μ±: μΉμμΌμ λ€μν μ€μκ° κΈ°λ₯μ λν νμ₯μ±μ΄ λμ΅λλ€. νλ‘μ νΈμ λ°μ μ λ°λΌ λ€μν μ€μκ° μνΈμμ© κΈ°λ₯μ μΆκ°ν λ, μΉμμΌμ μλ°©ν₯ ν΅μ λ₯λ ₯μ νμ©νλ©΄ λ³΄λ€ ν¨κ³Όμ μΌλ‘ ꡬνν μ μμμ κ²μ λλ€.
κ²°λ‘ μ μΌλ‘, SSEλ νμ¬ μ ν¬ νλ‘μ νΈμμ νμν κΈ°λ₯μ λΉ λ₯΄κ³ κ°νΈνκ² κ΅¬ννλ λ°μλ μΆ©λΆνμμ΅λλ€. νμ§λ§ λ―Έλμ νλ‘μ νΈ νμ₯μ±μ κ³ λ €νλ€λ©΄, μΉμμΌμ λ§μ κΈ°λ₯μ λ³΄λ€ ν¨κ³Όμ μΌλ‘ ꡬνν μ μλ κ°λ ₯ν λκ΅¬λ‘ μμ©ν κ² κ°μ΅λλ€. λ°λΌμ, λ€μ λ¨κ³μ κ°λ°μμλ μΉμμΌμ λμ λ° νμ©μ κ³ λ €ν΄λ³Ό νμκ° μλ€κ³ μκ°λ©λλ€. μ΄λ₯Ό ν΅ν΄ μ¬μ©μ κ²½νμ λμ± ν₯μμν€κ³ , νλ‘μ νΈμ κΈ°μ μ νκ³λ₯Ό λν μ μμ κ²μΌλ‘ κΈ°λλ©λλ€.
μμ²ν΄λΉμμλ λ‘κ·ΈμΈμ ν΅ν΄ μ¬μ©μμ λ°μ΄ν°λ₯Ό μλ²μ μμ νκ² μ μ₯νλ©°, μ΄λ κΈ°κΈ°μμλ μΈμ λ μ κ·Όν μ μλλ‘ JWT ν ν° κΈ°λ°μ λ‘κ·ΈμΈμ ꡬννμ΅λλ€. μ΄λ μ£Όμ κ³ λ €μ¬νμ ν΄λΌμ΄μΈνΈ μΈ‘μμ ν ν°μ μ΄λ»κ² μμ νκ² μ μ₯νλ©°, ν ν°μ κ°±μ κ³Ό λ§λ£ μ²λ¦¬λ₯Ό μ΄λ»κ² μ§νν κ²μΈμ§μμ΅λλ€.
- XSS 곡격μΌλ‘λΆν° 보νΈν μ μλ ν ν° μ μ₯ λ°©λ²
- μ¬μ©μμ ν ν°μ΄ νμ·¨λΉνλ μ μ©μ λ§λ λ°©λ²
- JavaScript μ κ·Όμ μ°¨λ¨νλ HttpOnly μΏ ν€ μ ν
- μ‘μΈμ€ ν ν°μ μλͺ
μ μ§§κ² νκ³ , 리νλ μ ν ν°μ μ¬μ©νμ¬ λ§λ£λ μ‘μΈμ€ ν ν°μ μ¬λ°κΈ
- Axios Interceptorsλ₯Ό νμ©ν ν ν° μ¬λ°κΈ λ©μ»€λμ¦ Axiosμ Interceptors κΈ°λ₯μ νμ©νμ¬ API νΈμΆ μ μ ν ν°μ μ ν¨μ±μ κ²μ¦νκ³ , λ§μ½ ν ν°μ΄ λ§λ£λμμ κ²½μ°, 리νλ μ ν ν°μ ν΅ν΄ μλ‘μ΄ μ‘μΈμ€ ν ν°μ λ°μμμ API νΈμΆμ μννλλ‘ κ΅¬ννμμ΅λλ€.
api.interceptors.response.use(
(response) => response,
async (error) => {
if (error.response.status === 401) {
const res = await api.post(
`${process.env.REACT_APP_SERVER_DOMAIN}/api/auth/refreshToken`,
{},
{ withCredentials: true },
);
const newAccessToken = res.data.accessToken;
api.defaults.headers.common['Authorization'] = `Bearer ${newAccessToken}`;
error.config.headers['Authorization'] = `Bearer ${newAccessToken}`;
return api(error.config);
}
return Promise.reject(error);
},
);
- 리νλ μ ν ν° λ§λ£ μ μ¬μ©μλ₯Ό λ‘κ·ΈμΈ νμ΄μ§λ‘ 리λ€μ΄λ νΈ
- μ΄μ: λ‘컬μμ μΏ ν€λ₯Ό ν΅ν ν ν° μ λ¬ λ¬Έμ λ°μ
- ν΄κ²°: CORS μ€μ μμ λ°
withCredentials
μ΅μ νμ±ν
μΏ ν€μ CORS μ€μ μ μ€μμ±μ μΈμνκ³ , μ΄λ₯Ό ν΅ν΄ ν΄λΌμ΄μΈνΈμ μλ² κ°μ 보μ λ¬Έμ λ₯Ό ν΄κ²°νμμ΅λλ€.
1μ£Όμ°¨
- μμ΄λμ΄ νμ
- κΈ°μ μ€ν μ‘°μ¬ λ° μ μ
- Git μμ νλ‘μ° κ²°μ
- νμ κ·μΉ μ 립
- Mockup μ μ λ° λμμΈ μ€κ³
- DB μ€ν€λ§ μ€κ³
- API Docs μμ
- κ°λ° μΌμ μΉΈλ° λ³΄λ μμ±
- κ°λ° μ΄κΈ° μΈν
2μ£Όμ°¨ ~
- νλ‘ νΈμλ μΉ μ¬μ΄νΈ ꡬν
- λ°±μλ μλ² κ΅¬ν
- 리ν©ν λ§ λ° λ²κ·Έ ν½μ€
- ν μ€νΈ μ½λ μμ±
- ν νλ‘μ νΈ λ°ν
- 리λλ―Έ μμ±
- λ°°ν¬
νλ‘ νΈμλ
λ°±μλ
μμνκΈ° μ μ, λ£¨νΈ λλ ν 리μ .env νμΌμ λ§λ€κ³ μλμ κ°μ΄ μ€μ ν΄μ£ΌμΈμ.
# ν΄λΌμ΄μΈνΈ
REACT_APP_SERVER_DOMAIN= # λ°±μλ API μλ² μ£Όμ
REACT_APP_GOOGLE_CLIENT_ID= # Google OAuth 2.0 μΈμ¦μ μν κ°
REACT_APP_AWS_ACCESS_KEY_ID= # AWS μλΉμ€μ νλ‘κ·Έλλ° λ°©μμΌλ‘ μ‘μΈμ€νκΈ° μν κ°
REACT_APP_AWS_SECRET_ACCESS_KEY= # AWS μλΉμ€μ νλ‘κ·Έλλ° λ°©μμΌλ‘ μ‘μΈμ€νκΈ° μν κ°
REACT_APP_REDIRECT_URI= # OAuth μΈμ¦ κ³Όμ μ€ μ¬μ©μλ₯Ό 리λ€μ΄λ νΈμν€λ URI
# μλ²
MONGODB_URI= # MongoDB λ°μ΄ν°λ² μ΄μ€ μ°κ²°μ μν URI
ACCESS_TOKEN_SECRET= # JWT μμ± λ° κ²μ¦μ μν λΉλ° ν€
REFRESH_TOKEN_SECRET= # JWT μμ± λ° κ²μ¦μ μν λΉλ° ν€
CLIENT_DOMAIN= # νλ‘ νΈμλ ν΄λΌμ΄μΈνΈμ λλ©μΈ μ£Όμλ IP μ£Όμ
AWS_ACCESS_KEY_ID= # AWS μλΉμ€μ νλ‘κ·Έλλ° λ°©μμΌλ‘ μ‘μΈμ€νκΈ° μν κ°
AWS_SECRET_ACCESS_KEY= # AWS μλΉμ€μ νλ‘κ·Έλλ° λ°©μμΌλ‘ μ‘μΈμ€νκΈ° μν κ°
λ¬Έμ μ¬νμ΄ μμΌμλ€λ©΄, μλμ μ΄λ©μΌλ‘ μ°λ½μ£ΌμΈμ.
- μ‘°μν: [email protected]
- μμ§μ: [email protected]
- μ°μ§ν: [email protected]
-
λΌμ΄μΌμ€
μ΄ νλ‘μ νΈλ MIT λΌμ΄μΌμ€λ₯Ό λ°λ¦ λλ€. λ μμΈν λ΄μ©μ LICENSE νμΌμ μ°Έκ³ ν΄μ£ΌμΈμ.