Axios, SMS/Email 전송

류연찬·2023년 5월 14일
0

GraphQL

목록 보기
4/17

Axios

Axios 는 브라우저, Node.js 를 위한 HTTP 비동기 통신 라이브러리입니다.

  • 운영 환경에 따라 브라우저의 XML HttpRequest 객체 또는 Node.js의 HTTP API 사용
  • Promise(ES6) API 사용
  • Async/Await(ES7) API 사용
  • 요청과 응답 데이터의 변형
  • HTTP 요청 취소 및 요청과 응답을 JSON 형태로 자동 변경

Axios 설치

프로젝트에 axios 를 설치하는 방법은 여러가지가 있지만 우리는 yarn 을 이용하여 설치하겠습니다.

npm install axios
or
yarn add axios

Axios 응답 제어

.then

비동기 통신이 성공했을 경우, .then() 은 콜백을 인자로 받아 결과값을 처리할 수 있습니다.

.catch

.catch() 를 통해 오류를 처리합니다.

error 객체에서는 오류에 대한 주요 정보를 확인할 수 있습니다.

axios.get('/hello').
	catch((error) => {
  		if(error.response) {
          console.log(error.response.status);
          console.log(error.response.headers);
        }
	})

위와 같이 .catch 에서 받아오는 error 객체를 통해 error.response.status 응답 상태 코드와 error.response.headers 응답 헤더 정보를 파악할 수 있습니다.

Axios HTTP 요청 메서드 종류

axios.get(url[, config])

서버에서 데이터를 가져올 때 사용하는 메서드입니다.

두번째 파라미터 config 객체에는 헤더(header), 응답 초과시간(timeout), 인자값(params) 등의 요청 값을 같이 넘길 수 있습니다.

axios.post(url[, data[, config]])

서버에 데이터를 새로 생성할 때 사용하는 메서드입니다.

두번째 파라미터로 생성할 데이터를 넘깁니다.

axios.put(url[, data[, config]])

특정 데이터를 수정할 때 요청하는 메서드입니다.

put 은 새로운 리소스를 생성하거나 이미 존재하는 데이터를 대체할 때 사용됩니다.

post 는 여러번 호출할 경우, 새로운 데이터가 지속적으로 추가됩니다.

반면, put 은 한 번 요청을 하거나 여러번 지속적으로 요청해도 결과값이 동일합니다.

예를 들어, 유저의 이름을 Batman 으로 수정하기 위해 axios.put 요청을 보냅니다.

이때, put 요청을 한 번 보내거나 여러번 보내도 유저의 이름은 Batman 으로 동일하게 수정됩니다.

axios.delete(url[, config])

특정 데이터 값을 삭제할 때 요청하는 메서드입니다.

Callback

callback 은 프로그래밍에서 빼놓을 수 없는 아주 중요한 개념입니다.

callback의 의미는 크게 2가지가 있습니다.

1. 다른 함수의 인자로 이용되는 함수
2. 이벤트에 의해 호출되어지는 함수

다른 함수의 인자로 이용되는 함수

function add(x, y)() {
  return x + y;
}

위에 add 함수에서는 인자로 x , y 가 들어갑니다.

보통 문자열, 숫자열, 배열, 객체 등의 데이터를 인자로 많이 넣어봤습니다.

하지만 인자에는 함수도 들어갈 수 있습니다.

한 번 예제를 통해 보도록 하겠습니다.

function add(x, y, callback) {
  let result = x + y;
  callback(result);
}

function result (data) {
  console.log(data, '콜백 함수 실행');
}

add(5, 10, result);

add 함수에는 인자로 x, y, callback 이 들어가고, callback에는 함수가 들어갑니다.

그리고 add 함수를 실행시키면 add 함수 안에서 인자로 들어간 다른 함수가 실행됩니다.

이럴 때 add 함수의 인자로 사용된 result 함수를 callback 함수라고 부릅니다.

이벤트에 의해 호출되어지는 함수

이벤트라도 하면 어떤 보통 onClick, onChange 등이 떠오릅니다.

onClick, onChange 는 html에서 미리 만들어 놓은 함수입니다.

보통 onCLick 을 아래처럼 사용합니다.

<button onClick={handleClickButton}></button>

위의 모습을 보면 button을 클릭하면 onClick 함수가 실행되고, onCLick에서는 다시 handleClickFunction을 실행하게 됩니다.

위에서 얘기했듯이 함수의 인자로 handleClickFunction이 들어가기 떄문에 handleClickFunctioncallback 함수라고 부를 수 있습니다.

이런 상황에서 우리는 handleClickFunction을 호출한다, 호출된디라고도 합니다.

이러한 callback 함수는 2가지로 다시 나눌 수 있습니다.

1. 동기적 함수
2. 비동기적 함수

동기(synchronous)적 방식 : 현재 실행 중인 코드가 완료된 후 다음 코드를 실행
비동기(asynchronous)적 방식 : 현재 실행 중인 코드의 완료 여부와 무관하게 즉시 다음 코드로 넘어가서 실행

자바스크립트는 Single-Thread / Non-Blocking 방식으로 코드를 실행 합니다.

간단하게 이야기 하면, 자바스크립트는 한 번에 하나의 코드만 실행할 수 있습니다. (Single-Thread)

하지만 코드를 실행하고 해당 결과를 기다리지 않고 다음 코드를 실행합니다. (Non-Blocking)

비동기적 callback

비동기적 callback 함수의 가장 좋은 예시는 setTimeout 입니다.

비동기적 함수는 결과를 기다리지 않고 다음 코드를 실행하는 과정을 말합니다.

function Test() {
  console.log('3초 기다리기');
}

setTimeout(Test, 3000);
console.log('이건 바로 실행');

위의 코드의 결과를 콘손을 통해 확인해보겠습니다.

'이건 바로 실행'
'3초 기다리기'

분명 setTimeout 을 먼저 실행시켰지만 하단에 있는 콘솔이 먼저 결과창에 보입니다.

이러한 이유는 자바스크립트 코드가 비동기적으로 코드를 실행시키기 떄문입니다.

Promise

위처럼 계속 비동기적 callback을 연속으로 사용하게 되는 경우에는 callback 지옥에 빠지게 됩니다.

callback 지옥

콜백 지옥(callback hell) 은 콜백 함수를 익명 함수로 전달하는 과정이 반복되어 코드의 들여쓰기 수준이 감당하기 힘들정도로 깊어지는 현상을 얘기합니다.

주로 이벤트 처리나 서버 통신과 같은 비동기적인 작업을 수행하기 위해 이런 형태가 자주 등장하는데, 가독성이 떨어지면서 코드를 수정하기 어렵습니다.

예시를 보면서 콜백 지옥이 어떻게 작동하는지 확인해보겠습니다.

setTimeout((name) => {
  let coffeeList = name;
  console.log(coffeeList);
  
  setTimeout((name) => {
    coffeeList += ', ' + name;
    console.log(coffeeList);
    
      setTimeout((name) => {
	    coffeeList += ', ' + name;
    	console.log(coffeeList);
        
          setTimeout((name) => {
            coffeeList += ', ' + name;
            console.log(coffeeList);
          }, 500, 'Latte');
	  }, 500, 'Mocha');
  }, 500, 'Americano');
}, 500, 'Expresso');

위 코드는 0.5초마다 커피 목록을 수집하고 출력합니다.

> 출력값
Expresso (0.5)
Expresso, Americano (1.0)
Expresso, Americano, Mocha (1.5)
Expresso, Americano, Mocha, Latte (2.0)

각 콜백의 커피 이름을 전달하고 목록에 이름을 추가합니다.

정상적으로 작동되지만 들여쓰기 수준이 과도하기 깊어지고 값이 아래에서 위로 전달되어 가독성이 떨어집니다.

이러한 것을 해결하기 위해 자바스크립트는 ES6Promise 를 추가했습니다.

axios는 기본적으로 Promise 를 지원하는 라이브러리입니다.

이번에는 Promise 와 axios를 이용해 Get 요청을 보내 data를 받아와보겠습니다.

var url = 'https://koreanjson.com/posts/1';

function getData() {
  return new Promise(function(resolve, reject) {
    axios.get(url).then(function(response) {
      if(response) {
        resolve(response.data);
      }
      reject(new Error('Request is failed'));
    });
  });
}

let result = [];

getData().then(function(data) {
  for(let v of Object.values(data)) {
    result.push(v);
  }
  console.log(result);
}).catch(function(err) {
  console.log(err);
})

new 연산자와 함께 호출한 Promise 의 인자로 넘겨주는 콜백 함수는 호출할 때 바로 실행되지만 그 내부에 resolve 또는 reject 함수를 호출하는 구문이 있을 경우 둘 중 하나가 실행되기전까지는 then 또는 catch 로 넘어가지 않습니다.

따라서 비동기 작업이 완료될 때 resolve 또는 reject 를 호출하는 방법으로 비동기 작업의 동기적 표현이 가능해집니다.

resolve 는 보통 성공했을 때 반환값을 의미하고 reject 는 보통 실패했을 경우를 나타냅니다.

async/await

axios는 기본적으로 async/await 를 지원하는 라이브러리입니다.

이번에는 async/await 와 axios를 이용해 GET요청을 보내 data를 받아와보겠습니다.

async function getData(url) {
  let data = (await axios.get(url)).data;
  return data;
}

let url = 'https://koreanjson.com/posts/1';

async function dataArr() {
  let data = await.getData(url);
  let result = [];
  for(let v of Object.values(data)) {
    result.push(v);
  }
  console.log(result);
}

dataArr();

위 코드는 ES2017에 추가된 async/await 를 이용한 코드입니다.

비동기 작업을 수행하고자 하는 함수 앞에 async 를 표기하고, 함수 내부에서 실질적인 비동기 작업 필요한 위치마다 await 를 표기하는 것만으로 뒤의 내용을 Promise 로 자동 전환하고 해당 내용이 resolve 된 이후에야 다음으로 진행됩니다.

await 의 유무로 동기적 함수와 비동기적 함수를 구분할 수 있습니다.

async/await 실습

06-01-async-await 폴더를 만들고, 안에 index.js 파일을 만들어주세요.

터미널에서 해당 폴더로 이동한 후, yarn init 또는 npm init 을 통해 package.json 파일을 만들어줍니다.

만들어진 package.json 파일에 "type": "module" 한 줄을 추가해줍니다.

{
  "name": "06-01-async-await",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "type": "module"
}

axios를 설치하기 위해 터미널에서 yarn add axios 또는 npm install axios 를 입력해주어 패키지를 설치해줍니다.

이제 index.js 파일로 들어가 axiosimport 해줍니다.

import axios from "axios";

function fetchPost() {

}

fetchPost();

fetchPost 함수 안에 await을 쓰기 위해서 함수 앞에 async를 붙여주고, axios를 이용해 데이터를 받아오고, 그 결과를 출력하는 코드를 작성해줍니다.

import axios from "axios";

async function fetchPost() {
    const result = await axios.get('https://koreanjson.com/posts/1');
    console.log(result);
}

fetchPost();

터미널에서 node index.js 를 통해 파일을 실행해 주세요.

그러면 아래와 같은 결과가 출력되는 것을 확인할 수 있습니다.



SMS 전송

이전에는 핸드폰 번호를 받아 토큰을 생성해 콘솔에서 확인해보는 부분까지 진행했었습니다.

이번에는 외부 API를 이용해 문자를 전송해보겠습니다.

Coolsms - Node.js SDK 확인

API 사용 방법을 확인하기 위해 아래 사티으에서 Node.js SDK를 살펴보겠습니다.

Coolsms 사이트에 로그인이 되어야만 확인이 가능합니다.

https://console.coolsms.co.kr/oauth2/login

💡 SDK?
Software Development Kit의 약자로 개발자가 앱을 커스텀할 수 있도록 제공해주는 일종의 도구 모음과 같은 개념입니다.


Node.js SDK 를 살펴보면 아래와 같이 단문 문자 전송을 위한 예지를 확인할 수 있습니다.



API 작성

이제 실제로 문자를 보내보겠습니다.

04-04-rest-api-with-express-3-token 폴더를 복사하여 사본을 만든 후 폴더 이름을 06-02-rest-api-with-sms 으로 변경해줍니다.

그 안에 index.js 파일을 생성해주고, 04-04-rest-api-with-express-3-token 폴더에서 phone.js 파일을 복사해옵니다.

그리고 아래의 명령어를 통해 모듈을 설치해줍니다.

npm install coolsms-node-sdk
or
yarn add coolsms-node-sdk

phone.js 로 가서 coolsms-node-sdkimport 해줍니다.

import coolsms from 'coolsms-node-sdk'

이제 sendTokenToSMS 함수를 수정하겠습니다.

아래와 같이 내용을 입력하여 수정해주세요.

동시에 import한 coolsms에서 SDK 를 사용할 수 있도록 데이터를 가져오는 코드를 추가합니다.

export function sendTokenToSMS(phone, token) {
    const SMS_KEY = '본인의 Coolsms API Key'
    const SMS_SECRET = '본인의 Coolsms API Secret'
    const SMS_SENDER = '발신번호로 등록했던 본인의 휴대폰 번호'

    const mySms = coolsms.default;  // SDK 가져오기

    console.log(phone + '번호로 인증번호' + token + '을 전송합니다.');
}

SDK 예시를 참고해 요청을 보내는 코드를 작성합니다.

여기서는 비동기 작업를 진행하기때문에 async/await 를 사용해야합니다.

export async function sendTokenToSMS(phone, token) {
    const SMS_KEY = '본인의 Coolsms API Key'
    const SMS_SECRET = '본인의 Coolsms API Secret'
    const SMS_SENDER = '발신번호로 등록했던 본인의 휴대폰 번호'

    const mySms = coolsms.default;  // SDK 가져오기
    const messageService = new mySms(SMS_KEY, SMS_SECRET);
    const result = await messageService.sendOne({
        to: phone,
        from: SMS_SENDER,
        text: `안녕하세요! 요청하신 인증번호는 ${token}입니다.`,
    })

    console.log(phone + '번호로 인증번호' + token + '을 전송합니다.');
}

환경변수 분리

앞서 만들었던 sendTokenToSMS 함수를 수정해보겠습니다.

소스코드에서 바로 사용하였던 중요한 키값들을 변수로 꺼내어 따로 값을 할당해 줍니다.

06-02-rest-api-with-sms 폴더에서 .env 파일을 만들어 줍니다.

파일에서 새로운 변수를 선언하고 본인의 APIKey, SecretKey 등의 값을 할당해 줍니다.

여기서 값을 할당할 때 따옴표는 생략합니다.

SMS_KEY = 본인의 Coolsms API Key
SMS_SECRET = 본인의 Coolsms API Secret
SMS_SENDER = 발신번호로 등록했던 본인의 휴대폰 번호

이제 .env 파일에서 설정한 값들이 sendTokenToSMS 함수에 적용될 수 있게 코드를 수정합니다.

export async function sendTokenToSMS(phone, token) {
    const SMS_KEY = process.env.SMS_KEY;  // 본인의 Coolsms API Key 입력
    const SMS_SECRET = process.env.SMS_SECRET;   // 본인의 Coolsms API Secret 입력
    const SMS_SENDER = process.env.SMS_SENDER;

    const mySms = coolsms.default;  // SDK 가져오기
    const messageService = new mySms(SMS_KEY, SMS_SECRET);
    const result = await messageService.sendOne({
        to: phone,
        from: SMS_SENDER,
        text: `안녕하세요! 요청하신 인증번호는 ${token}입니다.`,
    })

    console.log(phone + '번호로 인증번호' + token + '을 전송합니다.');
}

하지만 종종 환경변수 파일을 읽어오지못해 process.env... 에서 에러가 발생할 수도 있습니다.

파일을 읽어올 수 있게 라이브러리 하나를 설치해주겠습니다.

npm install dotenv
or
yarn add dotenv

그리고 phone.jsimport 해줍니다.

import 'dotenv/config'

이제 process.env 라는 명령어를 사용하여 변수를 선언하게 되면 해당 명령어가 .env 의 파일의 키값을 찾아 읽어줍니다.

그리고 .gitignore 파일에 .env 를 추가하여 소스코드가 공유될 수 없도록 설정해줍니다.

이제 터미널에서 해당 폴더로 이동하여 서버를 띄워주고 postman으로 문자를 받을 핸드폰 번호를 입력하고 Send를 눌러 요청합니다.

터미널에 콘솔이 잘 찍히고, 문자도 정상적으로 받았습니다.

Email 전송

이번에는 이메일을 실제로 전송해보겠습니다.

API 작성

Nodemailer의 공식 문서를 참고해 주세요.

https://nodemailer.com/about/

06-02-rest-api-with-sms 폴더를 복사하여 사본을 만들어주고 폴더명을 06-03-rest-api-with-sms-email 로 변경해줍니다.

아래의 명령어를 입력해 Nodemailer를 설치해주세요.

npm install nodemailer
or
yarn add nodemailer

02-06-welcome-template-destructuring-api-date 폴더 안에 있는 date.js 파일과 email.js 파일을 복사해 가져옵니다.

// date.js 파일

export function getCreatedAt() {
    const date = new Date();
    const yyyy = date.getFullYear();
    const mm = date.getMonth() + 1;
    const dd = date.getDate();
    return `${yyyy}-${mm}-${dd}`;
}

email.js 에 코드를 조금 수정해주겠습니다.

// email.js 파일

import { getCreatedAt } from './date.js'

// 이메일 정규식
const email_reg = /^[A-Za-z0-9_\.\-]+@[A-Za-z0-9\-]+\.[A-Za-z0-9\-]+/;

export function checkValidateEmail(email) {
  	// 이메일 유효성 검사를 하여 통과되면 true를 반환
    if(email_reg.test(email) === false) {
        // 에러 발생을 알려준다.
        console.log('에러발생! 이메일을 제대로 입력해 주세요.');
        return false;
    }

    return true;
}

export function getWelcomeTemplate({name, age, school}) {
    const createdAt = getCreatedAt();

    return`
        <html>
            <body>
                <h1>${name}님 가입을 환영합니다.</h1>
                <hr/>
                <span>이름 : ${name}</span>
                <span>나이 : ${age}</span>
                <span>학교 : ${school}</span>
                <span>가입일 : ${createdAt}</span>
            </body>
        </html>
    `
}

export function sendWelcomeTemplateToEmail(email, template) {
    // 테플릿을 이메일에 전송
    console.log(`${email}로 템플릿 ${template}을 전송합니다.`)
}

가입 환영 이메일을 보내는 API를 index.js 파일에 추가해보겠습니다.

import { checkValidateEmail, getWelcomeTemplate, sendWelcomeTemplateToEmail } from './email.js'

// ... 기존 코드

app.post('/users', (req, res) => {
    const user = req.body.myuser;

    // 1. 이메일이 정상인지 확인한다(1-존재여부, 2-'@'포함여부)
    const isValid = checkValidateEmail(user.email);
  
    if(isValid) {
        // 2. 가입 환영 템플릿 만들기
        const myTemplate = getWelcomeTemplate(user);
        
        // 3. 이메일에 가입 환영 템플릿 추가하기
        sendWelcomeTemplateToEmail(user.email, myTemplate);
        res.send('가입완료!')
    }
})

app.listen(3000, () => {
    console.log(`Example app listening on port ${3000}`)
});

이제 email.js 파일로 가서 설치한 Nodemailerimport 하고 메일을 전송하는 코드를 작성하겠습니다.

마찬가지로 비동기 작업을 수행하니 async/await 를 사용합니다.

import nodemailer from 'nodemailer';

// ... 기존 코드

export async function sendWelcomeTemplateToEmail(email, template) {
    const EMAIL_USER = "구글 계정 2단계 인증을 설정한 본인 이메일";
    const EMAIL_PASS = "구글 계정 관리에서 발급 받았던 본인의 앱 비밀번호";
    const EMAIL_SENDER = "발신자 이메일";

    const transporter = nodemailer.createTransport({
        service: 'gmail',
        auth: {
            user: EMAIL_USER,
            pass: EMAIL_PASS
        }
    })

    const result = await transporter.sendMail({
        from: EMAIL_SENDER,
        to: email,
        subject: '가입을 축하합니다!!!',
        html: template
    });

    console.log(result);
    console.log(`${email}로 템플릿 ${template}을 전송합니다.`)
}

이제 해당 폴더로 이동해 서버를 실행해주세요.

postman을 열어서 http://localhost:3000/users 으로 POST 요청을 보내겠습니다.

Body 부분에 JSON 형식으로 아래와 같이 user 정보를 적어주세요.

email 에는 이메일을 받을 주소를 입력해주세요.

메일이 정상적으로 수신된 것을 확인할 수 있습니다.


환경변수 분리

SMS 전송 때와 마찬가지로 .env 파일에 이메일 전송 관련 환경 변수를 관리해주겠습니다.

EMAIL_PASS = 구글 계정 관리에서 발급 받았던 본인의 앱 비밀번호
EMAIL_SENDER = 구글 계정 2단계 인증을 설정한 본인 이메일
EMAIL_USER = 구글 계정 2단계 인증을 설정한 본인 이메일

그리고 환경변수가 잘 적용되도록 sendWelcomeTemplateEmail 함수의 코드도 수정해줍니다.

import 'dotenv/config.js'

// ... 기존 코드

export async function sendWelcomeTemplateToEmail(email, template) {
    const EMAIL_USER = process.env.EMAIL_USER; // 구글 계정 2단계 인증을 설정한 본인 이메일
    const EMAIL_PASS = process.env.EMAIL_PASS;  // 구글 계정 관리에서 발급 받았던 본인의 앱 비밀번호
    const EMAIL_SENDER = process.env.EMAIL_SENDER;

    const transporter = nodemailer.createTransport({
        service: 'gmail',
        auth: {
            user: EMAIL_USER,
            pass: EMAIL_PASS
        }
    })

    const result = await transporter.sendMail({
        from: EMAIL_SENDER,
        to: email,
        subject: '가입을 축하합니다!!!',
        html: template
    });

    console.log(result);
    console.log(`${email}로 템플릿 ${template}을 전송합니다.`)
}

0개의 댓글