배너 이미지

React, Node.js를 이용한 영어 단어 공부 앱 제작기

최종 수정일 : (1년 전)

🏁 시작

어쩌다 보니 팔자에 없던 영어 과외를 진행하게 되었습니다.

자연스럽게 단어 시험을 쳐야 했고, 아무래도 웹앱 형태로 진행하는 게 편하지 않을까 싶은 제 판단(과 같이 수업하시는 분의 당당한 요청)에 의해 단어 공부 및 시험을 위한 웹앱을 제작했습니다. 살인적인 1주일이란 마감일과 함께요.

어디가서 코딩한다고 말해봤자 얻는 건 학부생의 '혹시 xx 언어도 다룰 줄 아세요?'라는 질문과 다양한 사람의 '혹시 이런 것도 만드실 수 있나요?'라는 질문뿐이니 '혹시 어떤 일 하시느냐'는 질문에 가장 올바른 대답은 '그냥 한량입니다'가 아닐까 싶습니다.

🏗️ 설계

영어 앱 - 설계 및 계획

일단 마음을 먹으면 진심의 200%를 부어다가 끝은 보고 살았기에, 일단 설계부터 시작했습니다.
마음 가는 대로 만들어서 이런저런 오류나 문제가 생기면 도무지 일주일이란 기간 내에 만들 수 없어 보이기도 했고요.

아무래도 아직 설계에 필요한 안목과 경험은 부족해서 만드는 와중에 많은 수정이 필요하긴 하지만, 안 하는 것보단 항상 나았습니다.

프론트는 Snowpack으로 빌드한 React 앱으로, 백엔드는 ts-node-dev의 도움을 받아 Node.js로 제작했습니다. DB엔 MongoDB를 이용했고요.
마침 이미지 캐시 서버로 쓰던 서버가 놀고 있어서 백엔드 배포는 거기다가 했습니다.

🌐 프론트엔드

React 웹 앱

영어 앱 - 유저 페이지들

로그인하면 각종 페이지로 갈 수 있는 버튼과, 마지막으로 시험을 친 날짜, 성적이 표시되는 달력이 출력되게 해뒀습니다.

단어

단순하게 단어 목록을 출력하고, speechSynthesis를 이용해 발음을 들을 수 있게 해뒀습니다.

암기

영어단어가 출력되고 3초 뒤에 한글 뜻이 출력되게 해 암기에 도움을 주고자 만든 페이지입니다.
종이책이 있긴 하지만 아무래도 휴대성은 휴대폰을 넘기 힘드니까요.

위 단어 페이지와 더불어 만들 필요는 전혀 없었던 페이지인데, 단순히 제 200%의 진심이 낳은 결과물입니다.

시험

핵심답게 참 만들기 까다로웠던 페이지입니다.

  1. 그날 시험 칠 단어들을 받아와 시험 문제 개수만큼 무작위로 추출하기
  2. 매일 3 ~ 4개가량의 숙어가 있으니 숙어는 따로 추출해 위 배열에 합치기
  3. 답안인 단어를 포함해 무작위로 보기 생성하기
  4. 앞서 답으로 나왔던 단어는 보기에 포함하지 않기
  5. 숙어에선 최대한 숙어만 포함해 따로 보기 보여주기
  6. 부정행위 방지를 위해 문제당 5초로 시간제한 걸기

등 신경 쓸 것도 많고 관리할 상태도 많아 여러모로 어렵다기보단 귀찮을 게 많은 페이지였습니다…

영어 앱 - 관리자 페이지들

관리자 페이지

  1. 사용자가 마지막으로 친 날짜와 성적 표시
  2. 단어 json 파일 관리
  3. 시험 관련 설정

등을 하기 위한 관리자용 페이지도 만들었습니다.

결과물을 보면서도 느꼈지만, 정리하면서 새삼 수업의 규모에 비해 과하게 공들여 만든 것 같단 생각이 좀 드네요.

PWA

설치만 되게 하는 게 목표여서, 블로그에 쓴 json 파일과 서비스 워커 가져다가 대충 PWA 조건만 충족하게 해뒀습니다.
아무래도 PWA로 만드는 건 아직 그냥 손수 하는 게 제일 편한 것 같네요.

👷 백엔드

CRUD 구현, 사용자 관리(가입, 인증 등) 등 처음 해보는 게 많아 걱정이 좀 있었는데, 그리 복잡한 수준의 것을 필요로 하지 않으니 생각보다 간단했습니다.

사용자

사용자는 MongoDB에 저장하고 jwt로 인증 절차를 거치게 해뒀습니다.
내부에서만 쓰는 거긴 하지만, 그래도 혹시 모르니 소금(salt, 솔트)도 치고, bcrypt로 해시된 값을 저장하게도 해뒀습니다.

mongoose, jwt, bcrypt 세 패키지만 설치하니 정말 어려울 게 딱히 없더라고요.

단어

단어는 뭔가 MongoDB에 저장하는 것보다 쉽게 가는 길이 있을 것 같아 고민을 많이 했는데
요일을 나타내는 문자열이 YYYY/MM/DD인 걸 곰곰이 보다가 Node.js가 실행되는 상위 디렉토리에 words란 디렉토리 를 만들고 YYYY 디렉토리 안에 MM 디렉토리 안에 DD.json을 넣으면 되겠거니 싶어서 그렇게 진행했습니다.

처음엔 YYYY-MM-DD.json으로 만들려 했는데, 그러면 관리자 페이지에서 달력에 단어 추가 여부를 표시하려니 최적화가 참 힘들더라고요. 위 방식으로 진행하니 그냥 ../words/YYYY/MM 디렉토리를 readdirSync로 읽어다 보내주기만 하면 끝이니 최적화도 구현도 훨씬 쉬웠습니다.

app.post("/words/add"authMiddleware, (requestresponse=> {
    // Check if admin
    const { userfreshToken } = request;

    if (!user.isAdmin)
        return response.send({
            errortrue,
            freshToken,
            message"You are not authorized",
        });

    // Add words
    const { namewords } = request.body;
    const [yearmonth= name.split("/");
    const dirPath = path.join("../words/"`${year}/${month}`);

    if (!existsSync(dirPath)) {
        fs.mkdirSync(dirPath, { recursivetrue });
    }

    fs.writeFile(
        path.join("../words/"name),
        JSON.stringify(words),
        (error=> {
            if (error) {
                response.send({
                    errortrue,
                    freshToken,
                    message"Couldn't create file",
                });
            } else {
                response.send({
                    successtrue,
                    freshToken,
                    message`${name} successfully created!`,
                });
            }
        }
    );
});

이대로 끝내기는 뻘쭘해서 첨부한 단어 추가 라우터에 쓴 코드입니다.

🎉 마무리

상술한 것처럼, 수업의 규모에 비해 좀 과한 앱을 만든 것 같지만, 항상 그래 왔듯 결과물 보니 뿌듯하고 기분은 좋네요.
Rest API 구축 등 처음 해본 것도 있으니 확실히 좋은 경험이기도 했고요.
수업 들으시는 분들 실력 향상에 조금이나마 도움이 된다면 금상첨화가 아닐까 싶습니다.

여하튼 마지막으로 다시 한 번 강조하자면, 어디 가서 코딩한다는 걸 드러내는 건 그리 좋은 선택이 아닐 수 있습니다…….


profile

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다

주의 : 비밀 댓글 사용 시 수정 기능을 이용할 수 있는 시간이 지나면 작성자도 내용 확인이 불가능합니다.