Home

(실전 프로젝트) localStorage를 활용한 나만의 메모장 웹 앱 만들기 - 데이터 저장부터 불러오기까지 완벽 구현

Published in javascript
December 30, 2025
2 min read
(실전 프로젝트) localStorage를 활용한 나만의 메모장 웹 앱 만들기 - 데이터 저장부터 불러오기까지 완벽 구현

안녕하세요, 코딩하는곰입니다! 🐾 오늘은 JavaScript의 강력한 브라우저 저장소 API인 localStorage를 활용해, 브라우저를 닫아도 데이터가 사라지지 않는 나만의 메모장 웹 애플리케이션을 만들어보려고 합니다. 프론트엔드 개발을 시작하신 지 얼마 안 되신 분들께는 localStorage가 무엇인지, 어떻게 사용하는지 궁금하실 텐데요. 이번 포스팅을 통해 이 기술의 개념을 명확히 이해하고, 실제로 동작하는 메모장을 직접 코딩해보는 시간을 가져보겠습니다. 간단해 보이지만, 웹 앱의 기본기가 되는 데이터 관리의 핵심을 배울 수 있는 아주 좋은 실습 프로젝트입니다. 지금 바로 코드 에디터를 열고 함께 따라와 주세요!

localStorage란 무엇인가? 웹 스토리지의 기본 개념 파헤치기

localStorage는 웹 브라우저에 내장된 클라이언트 사이드 스토리지 솔루션입니다. 사용자의 로컬 머신(브라우저)에 키-값(key-value) 쌍의 형태로 데이터를 저장할 수 있게 해주죠. 가장 큰 장점은 브라우저 세션이 끝나도(탭이나 창을 닫아도) 데이터가 유지된다는 점입니다. 이는 임시 저장에 사용되는 sessionStorage와의 결정적인 차이점이에요. 그렇다면 쿠키(Cookie)와는 무엇이 다를까요? 쿠키도 데이터를 저장하지만, 매 HTTP 요청마다 서버로 자동 전송되며 용량도 매우 제한적(약 4KB)입니다. 반면 localStorage는 일반적으로 도메인당 최소 5MB 이상의 공간을 제공하며, 서버로 자동 전송되지 않아 보안性和 효율性이 더 뛰어납니다. 따라서 사용자의 설정, 폼 데이터 자동 저장, 간단한 애플리케이션 상태 등 오프라인에서도 유지되어야 할 데이터를 저장하는 데 안성맞춤입니다. localStorage의 API는 놀라울 정도로 직관적이고 간단합니다. 주로 사용하는 메서드는 다음과 같습니다.

  • setItem(key, value): 데이터 저장
  • getItem(key): 데이터 조회
  • removeItem(key): 특정 데이터 삭제
  • clear(): 모든 데이터 삭제
  • key(index): 인덱스로 키 이름 조회
  • length: 저장된 아이템의 개수 여기서 중요한 점은 value문자열만 저장할 수 있다는 것입니다. 객체나 배열을 저장하려면 JSON.stringify()를 사용해 문자열로 변환(직렬화)해야 하고, 꺼내올 때는 JSON.parse()를 사용해 원래 자료형으로 변환(역직렬화)해야 합니다. 이 과정이 이번 메모장 프로젝트의 핵심 로직이 될 거예요.

(실전 프로젝트) localStorage를 활용한 나만의 메모장 웹 앱 만들기 - 데이터 저장부터 불러오기까지 완벽 구현
(실전 프로젝트) localStorage를 활용한 나만의 메모장 웹 앱 만들기 - 데이터 저장부터 불러오기까지 완벽 구현


📱 앱 개발에 도전하고 싶다면, (자바 내부 클래스 완벽 가이드) 정적/비정적 내부 클래스의 모든 것를 참고해보세요.

프로젝트 설계와 HTML/CSS 구조 잡기

먼저 우리가 만들 메모장의 기능을 정의해보죠.

  1. 메모 작성: 텍스트를 입력할 수 있는 영역.
  2. 메모 저장: 작성한 메모를 localStorage에 저장.
  3. 메모 목록 보기: 저장된 모든 메모를 목록으로 표시.
  4. 메모 불러오기: 목록에서 특정 메모를 클릭하면 다시 편집 영역에 불러오기.
  5. 메모 삭제: 개별 메모 또는 전체 메모 삭제 기능. 이제 간단한 HTML 구조를 만들어보겠습니다. 부트스트랩 같은 CSS 프레임워크를 사용하면 더 예쁘게 만들 수 있지만, 오늘은 순수 HTML/CSS/JS에 집중하기 위해 기본적인 스타일만 적용하겠습니다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>곰의 LocalStorage 메모장</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; font-family: 'Malgun Gothic', sans-serif; }
body { background-color: #f0f2f5; padding: 20px; color: #333; }
.container { max-width: 800px; margin: 0 auto; background: white; padding: 30px; border-radius: 15px; box-shadow: 0 5px 15px rgba(0,0,0,0.08); }
h1 { color: #2c3e50; margin-bottom: 20px; text-align: center; border-bottom: 3px solid #3498db; padding-bottom: 10px; }
.memo-input-section { margin-bottom: 30px; }
textarea { width: 100%; height: 150px; padding: 15px; border: 2px solid #ddd; border-radius: 8px; font-size: 16px; resize: vertical; transition: border 0.3s; }
textarea:focus { outline: none; border-color: #3498db; }
.button-group { margin-top: 15px; display: flex; gap: 10px; flex-wrap: wrap; }
button { padding: 12px 25px; border: none; border-radius: 6px; cursor: pointer; font-size: 16px; font-weight: bold; transition: all 0.2s ease; }
#saveBtn { background-color: #2ecc71; color: white; }
#saveBtn:hover { background-color: #27ae60; }
#clearBtn { background-color: #e74c3c; color: white; }
#clearBtn:hover { background-color: #c0392b; }
#newBtn { background-color: #3498db; color: white; }
#newBtn:hover { background-color: #2980b9; }
.memo-list-section h2 { color: #2c3e50; margin-bottom: 15px; padding-bottom: 5px; border-bottom: 2px solid #eee; }
#memoList { list-style: none; }
.memo-item { background: #f8f9fa; margin-bottom: 10px; padding: 15px; border-left: 5px solid #3498db; border-radius: 5px; display: flex; justify-content: space-between; align-items: center; cursor: pointer; transition: background 0.2s; }
.memo-item:hover { background: #e9ecef; }
.memo-content { flex-grow: 1; }
.memo-date { font-size: 0.85em; color: #7f8c8d; margin-top: 5px; }
.delete-btn { background-color: #95a5a6; color: white; border: none; padding: 5px 10px; border-radius: 3px; cursor: pointer; font-size: 0.9em; }
.delete-btn:hover { background-color: #7f8c8d; }
</style>
</head>
<body>
<div class="container">
<h1>🐾 코딩하는곰의 LocalStorage 메모장</h1>
<section class="memo-input-section">
<textarea id="memoInput" placeholder="여기에 메모를 입력하세요..."></textarea>
<div class="button-group">
<button id="saveBtn">💾 메모 저장</button>
<button id="newBtn">📄 새 메모</button>
<button id="clearBtn">🗑️ 전체 삭제</button>
</div>
</section>
<section class="memo-list-section">
<h2>📋 저장된 메모 목록</h2>
<ul id="memoList"></ul>
</section>
</div>
<script src="memo.js"></script>
</body>
</html>

(실전 프로젝트) localStorage를 활용한 나만의 메모장 웹 앱 만들기 - 데이터 저장부터 불러오기까지 완벽 구현
(실전 프로젝트) localStorage를 활용한 나만의 메모장 웹 앱 만들기 - 데이터 저장부터 불러오기까지 완벽 구현


홍보용 전단이나 SNS 콘텐츠에 맞춤형 QR 코드를 넣고 싶을 때는 컬러 커스터마이징이 가능한 QR 생성기를 사용해보세요.

JavaScript로 논리 구현하기 - 저장, 조회, 삭제의 완성

이제 본격적으로 localStorage를 조작하고 DOM을 제어하는 JavaScript 코드를 작성해볼까요? memo.js 파일을 만들고 아래 코드를 작성합니다. 각 함수와 로직에 상세한 주석을 달았으니 차근차근 이해해보세요.

// DOM 요소 참조
const memoInput = document.getElementById('memoInput');
const saveBtn = document.getElementById('saveBtn');
const newBtn = document.getElementById('newBtn');
const clearBtn = document.getElementById('clearBtn');
const memoList = document.getElementById('memoList');
// localStorage에서 사용할 키 이름
const STORAGE_KEY = 'bear_memo_app_memos';
// 페이지 로드 시 저장된 메모 목록을 렌더링
document.addEventListener('DOMContentLoaded', loadMemos);
// 1. 메모 저장 함수
saveBtn.addEventListener('click', saveMemo);
function saveMemo() {
const memoText = memoInput.value.trim();
if (memoText === '') {
alert('메모 내용을 입력해주세요!');
return;
}
// 기존 메모 배열을 가져오거나, 없으면 새 배열 생성
const memos = getMemosFromStorage();
// 새 메모 객체 생성 (고유 ID와 시간戳 추가)
const newMemo = {
id: new Date().getTime(), // 간단한 고유 ID 생성
content: memoText,
createdAt: new Date().toLocaleString('ko-KR') // 한국 시간 형식
};
// 새 메모를 배열에 추가
memos.push(newMemo);
// 변경된 배열을 localStorage에 저장 (문자열로 변환)
localStorage.setItem(STORAGE_KEY, JSON.stringify(memos));
// 입력창 초기화 및 목록 새로고침
memoInput.value = '';
loadMemos();
alert('메모가 저장되었습니다!');
}
// 2. localStorage에서 메모 배열을 가져오는 헬퍼 함수
function getMemosFromStorage() {
const storedMemos = localStorage.getItem(STORAGE_KEY);
// 저장된 데이터가 있으면 파싱, 없으면 빈 배열 반환
return storedMemos ? JSON.parse(storedMemos) : [];
}
// 3. 메모 목록을 화면에 렌더링하는 함수
function loadMemos() {
const memos = getMemosFromStorage();
memoList.innerHTML = ''; // 기존 목록 초기화
if (memos.length === 0) {
memoList.innerHTML = '<li class="memo-item" style="justify-content:center; color:#95a5a6;">저장된 메모가 없습니다.</li>';
return;
}
// 메모 배열을 역순으로(최신순) 표시
memos.reverse().forEach(memo => {
const li = document.createElement('li');
li.className = 'memo-item';
li.dataset.id = memo.id; // 데이터 속성에 ID 저장
li.innerHTML = `
<div class="memo-content">
<div>${memo.content}</div>
<div class="memo-date">작성일: ${memo.createdAt}</div>
</div>
<button class="delete-btn" onclick="deleteMemo(event, ${memo.id})">삭제</button>
`;
// 메모 아이템 클릭 시 편집창에 불러오기
li.addEventListener('click', (e) => {
// 삭제 버튼을 클릭한 경우는 제외
if (e.target.classList.contains('delete-btn')) return;
memoInput.value = memo.content;
// 현재 불러온 메모의 ID를 저장 (수정 시 활용 가능)
memoInput.dataset.currentId = memo.id;
});
memoList.appendChild(li);
});
}
// 4. 개별 메모 삭제 함수 (이벤트 버블링 방지를 위해 event 객체 전달)
function deleteMemo(event, id) {
event.stopPropagation(); // 상위 li 요소의 클릭 이벤트 발생 방지
if (!confirm('정말 이 메모를 삭제하시겠습니까?')) return;
const memos = getMemosFromStorage();
// 전달받은 id와 일치하지 않는 메모만 필터링
const updatedMemos = memos.filter(memo => memo.id !== id);
// 필터링된 새 배열을 저장
localStorage.setItem(STORAGE_KEY, JSON.stringify(updatedMemos));
loadMemos(); // 목록 다시 그리기
alert('메모가 삭제되었습니다.');
}
// 5. 새 메모 작성 버튼
newBtn.addEventListener('click', () => {
memoInput.value = '';
delete memoInput.dataset.currentId; // 현재 편집 중인 ID 정보 제거
memoInput.focus();
});
// 6. 전체 삭제 버튼
clearBtn.addEventListener('click', () => {
if (getMemosFromStorage().length === 0) {
alert('삭제할 메모가 없습니다.');
return;
}
if (confirm('정말 모든 메모를 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다.')) {
localStorage.removeItem(STORAGE_KEY); // 또는 clear()
loadMemos();
memoInput.value = '';
alert('모든 메모가 삭제되었습니다.');
}
});
// (선택) 입력창에서 Ctrl+Enter 눌러 저장하는 기능
memoInput.addEventListener('keydown', (e) => {
if (e.ctrlKey && e.key === 'Enter') {
saveMemo();
}
});

이제 완성되었습니다! HTML 파일을 브라우저에서 열고 메모를 입력, 저장, 삭제해보세요. 크롬 개발자 도구(F12)의 ‘Application’ 탭에서 ‘Local Storage’ 항목을 보면 우리가 저장한 데이터가 실제로 키-값 쌍으로 들어가 있는 것을 확인할 수 있습니다. 이것이 바로 localStorage의 힘이죠.

(실전 프로젝트) localStorage를 활용한 나만의 메모장 웹 앱 만들기 - 데이터 저장부터 불러오기까지 완벽 구현
(실전 프로젝트) localStorage를 활용한 나만의 메모장 웹 앱 만들기 - 데이터 저장부터 불러오기까지 완벽 구현


정확한 시간 기록이 필요한 실험이나 트레이닝에는 실시간 스톱워치 기능을 활용하는 것이 좋습니다.

지금까지 localStorage의 기본 개념부터 실전 적용까지, 나만의 메모장 웹 앱을 만들어보는 시간을 가졌습니다. 이 작은 프로젝트 하나로 우리는 클라이언트 사이드 데이터 저장의 핵심 원리, JSON의 직렬화/역직렬화, DOM 이벤트 핸들링, 그리고 기본적인 CRUD(생성, 읽기, 수정, 삭제) 로직을 경험해볼 수 있었습니다. 이 메모장은 더 많은 기능으로 확장할 수 있습니다. 예를 들어, 메모 수정 기능을 추가하거나, 메모에 카테고리 태그를 달고 필터링하는 기능, 심지어는 다크 모드 지원과 같은 사용자 설정을 localStorage에 저장하는 것도 좋은 아이디어겠죠. 여러분의 상상력에 따라 이 프로젝트를 더 풍성하게 키워나가 보세요. 프론트엔드 개발의 매력은 이렇게 직접 만든 것이 눈앞에서 동작한다는 것입니다. localStorage는 그런 경험을 시작하게 해주는 훌륭한 도구입니다. 다음 시간에는 localStorage의 한계를 넘어서는 IndexedDB나 서버와의 연동에 대해 다뤄보도록 하겠습니다. 오늘도 코딩하는곰과 함께 즐거운 코딩 시간이 되셨길 바랍니다. 질문이나 아이디어가 있으시면 댓글로 남겨주세요! 감사합니다. 🐻💻

QR코드로 간편하게 번호를 확인하고 싶다면, AI 번호 추천과 최근 당첨번호까지 제공하는 지니로또AI 앱을 다운로드하세요.









최상의 건강을 위한 영양가득한 식품과 정보! life-plus.co.kr 바로가기
최상의 건강을 위한 영양가득한 식품과 정보! life-plus.co.kr 바로가기



다채로운 문화축제와 공연 소식을 공유하는 블로그! culturestage.co.kr 바로가기
다채로운 문화축제와 공연 소식을 공유하는 블로그! culturestage.co.kr 바로가기



비트코인 세계로의 첫걸음! 지금 가입하고 거래 수수료 할인 혜택 받으세요! bitget.com 바로가기
비트코인 세계로의 첫걸음! 지금 가입하고 거래 수수료 할인 혜택 받으세요! bitget.com 바로가기




Tags

#developer#coding#javascript

Share

Previous Article
(HTML/CSS 기초부터 실전까지) 글자 크기(font-size)와 두께(font-weight) 완벽 가이드 - 가독성과 SEO를 높이는 법

Related Posts

(자바스크립트 완전 정복) Uncaught ReferenceError 정의되지 않은 변수 접근, 원인과 100% 해결법
December 19, 2025
3 min