본문 바로가기
언어, 환경별 예제 코드

[rest api 예제] next.js- 카카오 로그인, 카카오 친구목록 조회, 메시지 발송

by kakao-TAM 2025. 2. 26.

next.js로 “카카오 로그인, 카카오 카카오 친구목록 조회, 메시지 발송” 테스트 해볼 수 있는 간단한 예제입니다.

[실행방법]

Git clone 후, npm install 합니다.

git clone https://github.com/kakao-tam/-Example-next.js.git
npm install
  1. 내 애플리케이션 > 앱 설정 > 요약 정보 > "REST API 키"를 복사해서 .env.local 파일 CLIENT_ID 항목에 설정합니다.
  2. 내 애플리케이션>제품 설정>카카오 로그인 > Redirect URI에 http://localhost:3000/redirect 주소를 설정합니다.
  3. npm run dev 로 실행합니다.

[실행결과]

/next.config.ts

import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  /* config options here */
    images: {
        remotePatterns: [
            {
                protocol: "https",
                hostname: "k.kakaocdn.net",
                pathname: "/14/dn/btqCn0WEmI3/nijroPfbpCa4at5EIsjyf0/o.jpg",
            }
        ]
    },
};

export default nextConfig;

/.env.local

CLIENT_ID=your_kakao_developers_rest_api_key
CLIENT_SECRET=your_kakao_client_secret
REDIRECT_URI=http://localhost:3000/redirect
KAUTH_HOST=https://kauth.kakao.com
KAPI_HOST=https://kapi.kakao.com
SECRET_COOKIE_PASSWORD=your_secret_cookie_password_must_be_32_characters
DOMAIN=localhost

/app/redirect/route.ts

import {getAccessToken, setSessionAccessToken} from "@/app/kakao";
import {redirect} from "next/navigation";
import {NextRequest} from "next/server";
export async function GET(req:NextRequest) {
    const code = req.nextUrl.searchParams.get('code') || '';
    const access_token = await getAccessToken(code);
    await setSessionAccessToken(access_token);
    redirect('/');
}

/app/kakao.ts

'use server';

import {redirect} from "next/navigation";
import {cookies} from "next/headers";
import {getIronSession} from "iron-session";

export type SessionData = {
    token: string
}

export async function sessionOptions() {
    return {
        password: process.env.SECRET_COOKIE_PASSWORD || '',
        cookieName: 'exampleCookieName',
        cookieOptions: {
            maxAge: 60 * 60 * 2, // 2h
            secure: process.env.PHASE === 'production',
            domain: process.env.DOMAIN
        }
    }
}

export async function authorize(scope?: string) {
    let scope_param = '';
    if (scope) {
        scope_param = `&scope=${scope}`;
    }
    redirect(`${process.env.KAUTH_HOST}/oauth/authorize?client_id=${process.env.CLIENT_ID}&redirect_uri=${process.env.REDIRECT_URI}&response_type=code${scope_param}&client_secret=${process.env.CLIENT_SECRET}`);
}

export async function getAccessToken(code: string) {
    const response = await fetch(`${process.env.KAUTH_HOST}/oauth/token`, {
        method: 'POST',
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded',
        },
        body: `client_id=${process.env.CLIENT_ID}&client_secret=${process.env.CLIENT_SECRET}&code=${code}&grant_type=authorization_code&redirect_uri=${process.env.REDIRECT_URI}`,
    });
    const json = await response.json();
    return json?.access_token.toString();
}

export async function setSessionAccessToken(access_token: string) {
    const session = await getIronSession<SessionData>(
        await cookies(),
        await sessionOptions()
    )
    session.token = access_token
    await session.save()
}

export async function getSessionAccessToken() {
    const session = await getIronSession<SessionData>(
        await cookies(),
        await sessionOptions()
    )
    return session.token;
}

export async function profile() {
    const access_token = await getSessionAccessToken();
    const response = await fetch(`${process.env.KAPI_HOST}/v2/user/me`, {
        headers: {
            'Authorization': `Bearer ${access_token}`
        }
    });
    return JSON.stringify(await response.json());
}

export async function friends() {
    const access_token = await getSessionAccessToken();
    const response = await fetch(`${process.env.KAPI_HOST}/v1/api/talk/friends`, {
        headers: {
            'Authorization': `Bearer ${access_token}`
        }
    });
    return JSON.stringify(await response.json());
}

export async function message() {
    const access_token = await getSessionAccessToken();
    const param = 'template_object={"object_type":"text","text":"Hello, world!","link":{"web_url":"https://developers.kakao.com","mobile_web_url":"https://developers.kakao.com"}}'
    const response = await fetch(`${process.env.KAPI_HOST}/v2/api/talk/memo/default/send`, {
        method: 'POST',
        headers: {
            'Authorization': `Bearer ${access_token}`,
            'Content-Type': 'application/x-www-form-urlencoded',
        },
        body: param
    });
    return JSON.stringify(await response.json());
}

export async function friends_message(uuids: string) {
    const access_token = await getSessionAccessToken();
    const uuid_param = encodeURIComponent(`${uuids}`);
    const param = `receiver_uuids=[${uuid_param}]&template_object={"object_type":"text","text":"Hello, world!","link":{"web_url":"https://developers.kakao.com","mobile_web_url":"https://developers.kakao.com"}}`
    const response = await fetch(`${process.env.KAPI_HOST}/v1/api/talk/friends/message/default/send`, {
        method: 'POST',
        headers: {
            'Authorization': `Bearer ${access_token}`,
            'Content-Type': 'application/x-www-form-urlencoded',
        },
        body: param
    });
    return JSON.stringify(await response.json());
}

export async function logout() {
    const access_token = await getSessionAccessToken();
    const response = await fetch(`${process.env.KAPI_HOST}/v1/user/logout`, {
        method: 'POST',
        headers: {
            'Authorization': `Bearer ${access_token}`
        }
    });
    return JSON.stringify(await response.json());
}

export async function unlink() {
    const access_token = await getSessionAccessToken();
    const response = await fetch(`${process.env.KAPI_HOST}/v1/user/unlink`, {
        method: 'POST',
        headers: {
            'Authorization': `Bearer ${access_token}`
        }
    });
    return JSON.stringify(await response.json());
}

/app/page.tsx

'use client';
import {useState} from "react";
import Image from "next/image";
import {authorize, friends, friends_message, logout, message, profile, unlink} from "@/app/kakao";

export default function Home() {
  const [data, setData] = useState('');
  const [uuid, setUuid] = useState('');
  return (
    <div className="grid items-center justify-items-center gap-5 font-[family-name:var(--font-geist-sans)]">
      <main className="flex flex-col gap-8 row-start-2 items-center sm:items-start">
        <p>카카오 로그인 및 프로필 조회 예제</p>
        <ol className="list-inside list-decimal text-sm text-center sm:text-left font-[family-name:var(--font-geist-mono)]">
          <li className="mb-2">
            [KOE101, KOE004] 내 애플리케이션 &gt; 제품 설정 &gt; 카카오 로그인 &gt; 활성화 설정 : ON
          </li>
          <li>
            [KOE006] 내 애플리케이션 &gt; 제품 설정 &gt; 카카오 로그인 &gt; Redirect URI : http://localhost:3000/redirect
          </li>
        </ol>

        <div className="flex gap-4 items-center flex-col sm:flex-row">
          <button onClick={() => authorize()}
                  className="flex items-center justify-center text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5"
          >
            <Image
                src={"https://k.kakaocdn.net/14/dn/btqCn0WEmI3/nijroPfbpCa4at5EIsjyf0/o.jpg"}
                width={222}
                height={49}
                alt="kakao login"
            />
          </button>
          &gt;
          <button onClick={() => authorize('friends,talk_message')}
                  className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:min-w-44"
          >
            추가 항목 동의 받기 - 친구목록 조회와 메세지 발송 권한 획득
          </button>
        </div>
        <div className="flex gap-4 items-center flex-col sm:flex-row">
          <textarea
              className="bg-gray-200 rounded-lg shadow border p-2"
              rows={10}
              cols={80}
              id="contents"
              readOnly={true}
              value={data}
          >
            </textarea>
        </div>
        <div className="flex gap-4 items-center flex-col sm:flex-row">
          <button onClick={async () => setData(await profile())}
                  className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:min-w-44"
          >
            사용자 정보 가져오기
          </button>
        </div>
        <ol start={3}
            className="list-inside list-decimal text-sm text-center sm:text-left font-[family-name:var(--font-geist-mono)]">
          <li>
            친구 목록 조회로 UUID 가져와 메시지 보내기 : 친구도 동의 해야 조회 가능
          </li>
        </ol>
        <div className="flex gap-4 items-center flex-col sm:flex-row">
          <button onClick={async () => setData(await friends())}
                  className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:min-w-44"
          >
            친구 목록 가져오기
          </button>

          <button onClick={async () => setData(await message())}
                  className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:min-w-44"
          >
            기본 템플릿으로 메시지 보내기 - 나에게 보내기
          </button>
        </div>
        <div className="flex gap-4 items-center flex-col sm:flex-row">
          <input onChange={(e) => setUuid(e.target.value)}
                 type="text" value={uuid}
                 placeholder="UUID 입력 ex) &#34;AAA&#34;,&#34;BBB&#34; 쌍따옴표 포함"
                 className="bg-white rounded-lg shadow border p-2 w-80"
          />
          <button onClick={async () => setData(await friends_message(uuid))}
                  className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:min-w-44"
          >
            기본 템플릿으로 메시지 보내기 - 친구에게 보내기
          </button>
        </div>
        <div className="flex gap-4 items-center flex-col sm:flex-row">
          <button onClick={async () => setData(await logout())}
                  className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:min-w-44"
          >
            로그 아웃
          </button>
          <button onClick={async () => setData(await unlink())}
                  className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:min-w-44"
          >
            연결 끊기
          </button>
        </div>
      </main>
    </div>
  );
}

로그인에 관한 가이드 : REST API | Kakao Developers REST API

친구목록 관한 가이드 : REST API | Kakao Developers REST API

메시지에 관한 가이드 : 카카오톡 메시지: REST API | Kakao Developers 카카오톡 메시지: REST API

KOE006 에러 : Koe006 에러가 발생할 때

친구 api, 메시지 api 사용을 위한 체크 리스트 : 친구 api와 피커, 메시지 api 사용을 위한 체크 리스트

친구목록, 메시지 API 자주 겪는 에러 : [faq] 친구 목록 api, 메시지 전송 api를 연동하는 과정에서 자주 겪는 에러

메시지 API 권한 신청 방법 : '메시지 API' 사용 신청 방법 / How to request permission for 'Messaging API'

댓글