Todo API 가이드 (프론트엔드 개발자용)¶
최종 업데이트: 2026-01-11
개요¶
이 API는 Todo, Schedule, Tag를 관리하는 기능을 제공합니다.
핵심 개념¶
| 개념 | 설명 |
|---|---|
| Todo | 할 일 항목. 독립적인 엔티티로 존재합니다. |
| Schedule | 캘린더 일정. Todo에 deadline이 있으면 자동으로 생성됩니다. |
| TagGroup | 태그를 묶는 그룹. Todo는 반드시 하나의 그룹에 속해야 합니다. |
| Tag | 세부 분류용 태그. TagGroup에 속하며, Todo/Schedule에 연결할 수 있습니다. |
Todo와 Schedule의 관계¶
flowchart LR
subgraph Todo
T1["title: 회의 준비"]
T2["deadline: 2024-01-20T10:00:00Z"]
T3["tag_group_id: 업무 (필수)"]
T4["tags: [중요, 회의]"]
end
subgraph Schedule["Schedule (자동 생성)"]
S1["title: 회의 준비"]
S2["start_time: 2024-01-20T10:00:00Z"]
S3["end_time: 2024-01-20T11:00:00Z"]
S4["source_todo_id: Todo의 ID"]
S5["tags: [중요, 회의]"]
end
T2 -->|"deadline이 있으면<br/>자동 생성"| S2
T4 -.->|"태그 복사"| S5
데이터 모델¶
Todo¶
interface Todo {
id: string; // UUID
title: string; // 제목
description?: string; // 설명 (선택)
deadline?: string; // 마감일시 (ISO 8601, 선택)
tag_group_id: string; // 소속 그룹 ID (필수)
parent_id?: string; // 부모 Todo ID (트리 구조용, 선택)
status: TodoStatus; // 상태
created_at: string; // 생성일시
tags: Tag[]; // 연결된 태그 목록
schedules: Schedule[]; // 연관된 Schedule 목록
include_reason: TodoIncludeReason; // 목록에 포함된 이유 (항상 포함)
}
type TodoStatus =
| "UNSCHEDULED" // 일정 미지정
| "SCHEDULED" // 일정 지정됨
| "DONE" // 완료
| "CANCELLED"; // 취소됨
type TodoIncludeReason =
| "MATCH" // 필터 조건에 직접 매칭된 Todo (또는 필터 미사용 시)
| "ANCESTOR"; // 필터에 매칭된 Todo의 조상이라서 포함된 Todo
Schedule¶
interface Schedule {
id: string; // UUID
title: string; // 제목
description?: string; // 설명 (선택)
start_time: string; // 시작 시간 (ISO 8601)
end_time: string; // 종료 시간 (ISO 8601)
recurrence_rule?: string; // 반복 규칙 (RRULE 형식, 선택)
recurrence_end?: string; // 반복 종료일 (선택)
parent_id?: string; // 반복 일정 원본 ID (가상 인스턴스인 경우)
source_todo_id?: string; // 연결된 Todo ID (Todo에서 생성된 경우)
state: ScheduleState; // 상태
created_at: string; // 생성일시
tags: Tag[]; // 연결된 태그 목록
}
type ScheduleState = "PLANNED" | "IN_PROGRESS" | "COMPLETED";
TagGroup & Tag¶
interface TagGroup {
id: string; // UUID
name: string; // 그룹명
color: string; // 색상 코드 (#RRGGBB)
description?: string; // 설명 (선택)
goal_ratios?: Record<string, number>; // 태그별 목표 비율 (선택)
is_todo_group: boolean; // Todo 그룹 여부
created_at: string;
updated_at: string;
tags: Tag[]; // 그룹에 속한 태그 목록
}
interface Tag {
id: string; // UUID
name: string; // 태그명
color: string; // 색상 코드 (#RRGGBB)
description?: string; // 설명 (선택)
group_id: string; // 소속 그룹 ID
created_at: string;
updated_at: string;
}
REST API¶
Base URL¶
Todo API¶
Todo 생성¶
POST /v1/todos
Content-Type: application/json
{
"title": "회의 준비",
"description": "프레젠테이션 자료 준비",
"tag_group_id": "550e8400-e29b-41d4-a716-446655440000",
"tag_ids": ["660e8400-e29b-41d4-a716-446655440001"],
"deadline": "2024-01-20T10:00:00Z",
"parent_id": null,
"status": "UNSCHEDULED"
}
| 필드 | 타입 | 필수 | 설명 |
|---|---|---|---|
title |
string | ✅ | Todo 제목 |
description |
string | ❌ | 설명 |
tag_group_id |
UUID | ✅ | 필수! 소속 그룹 ID |
tag_ids |
UUID[] | ❌ | 연결할 태그 ID 목록 |
deadline |
datetime | ❌ | 마감일시 (있으면 Schedule 자동 생성) |
parent_id |
UUID | ❌ | 부모 Todo ID (트리 구조) |
status |
string | ❌ | 상태 (기본값: UNSCHEDULED) |
응답 (201 Created):
{
"id": "770e8400-e29b-41d4-a716-446655440002",
"title": "회의 준비",
"description": "프레젠테이션 자료 준비",
"deadline": "2024-01-20T10:00:00Z",
"tag_group_id": "550e8400-e29b-41d4-a716-446655440000",
"parent_id": null,
"status": "UNSCHEDULED",
"created_at": "2024-01-15T09:00:00Z",
"include_reason": "MATCH",
"tags": [
{
"id": "660e8400-e29b-41d4-a716-446655440001",
"name": "중요",
"color": "#FF0000",
"group_id": "550e8400-e29b-41d4-a716-446655440000"
}
],
"schedules": [
{
"id": "880e8400-e29b-41d4-a716-446655440003",
"title": "회의 준비",
"start_time": "2024-01-20T10:00:00Z",
"end_time": "2024-01-20T11:00:00Z",
"source_todo_id": "770e8400-e29b-41d4-a716-446655440002",
"state": "PLANNED",
"tags": [...]
}
]
}
Todo 목록 조회¶
GET /v1/todos
GET /v1/todos?group_ids=uuid1&group_ids=uuid2
GET /v1/todos?tag_ids=uuid1&tag_ids=uuid2
| 파라미터 | 타입 | 설명 |
|---|---|---|
group_ids |
UUID[] | tag_group_id가 해당 그룹인 Todo만 조회 |
tag_ids |
UUID[] | 지정된 태그를 모두 포함한 Todo만 조회 (AND 방식) |
필터링 시 트리(orphan) 처리
tag_ids로 필터링하면, 매칭된 Todo + 그 Todo의 모든 조상(ancestor) 을 함께 반환합니다.- 이때 각 Todo에는
include_reason이 항상 포함됩니다.- 매칭된 Todo:
include_reason = "MATCH" - 조상 Todo:
include_reason = "ANCESTOR"
- 매칭된 Todo:
tag_ids를 사용하지 않으면, 반환되는 모든 Todo는include_reason = "MATCH"입니다.
정렬 규칙 (백엔드 기본 정렬)
- status 순서:
UNSCHEDULED→SCHEDULED→DONE→CANCELLED - 같은 status 내에서는:
- deadline이 있는 항목이 먼저 (null은 뒤)
- deadline 오름차순
- created_at 내림차순
트리 무결성 (백엔드 보장)
parent_id는 존재하는 Todo여야 합니다. (없으면 400)- 자기 자신을 부모로 설정할 수 없습니다. (400)
- 부모/자식 Todo의
tag_group_id는 반드시 같아야 합니다. (400) - 순환 참조(cycle)를 만들 수 없습니다. (400)
Todo 단건 조회¶
Todo 수정¶
PATCH /v1/todos/{todo_id}
Content-Type: application/json
{
"title": "수정된 제목",
"deadline": "2024-01-25T14:00:00Z",
"tag_ids": ["tag-uuid-1", "tag-uuid-2"],
"status": "SCHEDULED"
}
주의
deadline 변경 시 동작: - deadline 추가: 새 Schedule 생성 (태그도 복사) - deadline 변경: 기존 Schedule 시간 업데이트 - deadline 제거 (null): 기존 Schedule 삭제
주의
tag_ids 변경 시 동작: - Todo의 태그 업데이트 - 연결된 Schedule의 태그도 함께 동기화
Todo 삭제¶
주의
삭제 동작: - 삭제된 Todo에 연결된 Schedule은 함께 삭제됩니다. - 자식 Todo는 삭제되지 않고 루트로 승격됩니다 (parent_id가 NULL로 변경) - 자식 Todo에 연결된 Schedule은 그대로 유지됩니다.
Todo 통계 조회¶
응답:
{
"group_id": "550e8400-e29b-41d4-a716-446655440000",
"total_count": 15,
"by_tag": [
{ "tag_id": "uuid1", "tag_name": "중요", "count": 5 },
{ "tag_id": "uuid2", "tag_name": "회의", "count": 3 }
]
}
TagGroup API¶
그룹 생성¶
POST /v1/tags/groups
Content-Type: application/json
{
"name": "업무",
"color": "#FF5733",
"description": "업무 관련 항목",
"is_todo_group": true
}
그룹 목록 조회 (태그 포함)¶
그룹 수정¶
그룹 삭제¶
주의
CASCADE 삭제: 그룹에 속한 모든 태그도 함께 삭제됩니다.
Tag API¶
태그 생성¶
POST /v1/tags
Content-Type: application/json
{
"name": "중요",
"color": "#FF0000",
"group_id": "550e8400-e29b-41d4-a716-446655440000"
}
태그 목록 조회¶
태그 수정¶
태그 삭제¶
GraphQL API¶
Endpoint¶
개발 환경에서는 Apollo Sandbox UI를 통해 쿼리 테스트 가능합니다.
Calendar 쿼리¶
캘린더 뷰에 필요한 일정 데이터를 조회합니다.
query GetCalendar($startDate: Date!, $endDate: Date!, $tagFilter: TagFilterInput) {
calendar(startDate: $startDate, endDate: $endDate, tagFilter: $tagFilter) {
days {
date
events {
id
title
description
startAt
endAt
createdAt
isRecurring
parentId
instanceStart
tags {
id
name
color
groupId
}
}
}
}
}
Variables:
{
"startDate": "2024-01-01",
"endDate": "2024-01-31",
"tagFilter": {
"tagIds": ["660e8400-e29b-41d4-a716-446655440001"],
"groupIds": null
}
}
태그 필터링 규칙¶
| 필터 | 방식 | 설명 |
|---|---|---|
tagIds |
AND | 지정된 태그를 모두 포함한 일정만 반환 |
groupIds |
OR | 해당 그룹의 태그 중 하나라도 있으면 반환 |
# 태그 필터링 입력 타입
input TagFilterInput {
tagIds: [UUID!] # AND 방식
groupIds: [UUID!] # OR 방식 (그룹 내 태그)
}
응답 타입¶
type Calendar {
days: [Day!]!
}
type Day {
date: Date!
events: [Event!]!
}
type Event {
id: UUID!
title: String!
description: String
startAt: DateTime!
endAt: DateTime!
createdAt: DateTime!
isRecurring: Boolean!
parentId: UUID # 반복 일정 원본 ID
instanceStart: DateTime # 가상 인스턴스 시작 시간 (수정/삭제용)
tags: [TagType!]!
}
type TagType {
id: UUID!
name: String!
color: String!
description: String
groupId: UUID!
createdAt: DateTime!
updatedAt: DateTime!
}
주요 기능¶
1. Todo-Schedule 자동 연동¶
Todo에 deadline을 설정하면 Schedule이 자동으로 생성됩니다.
// deadline이 있는 Todo 생성
const response = await fetch('/v1/todos', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
title: "프로젝트 마감",
tag_group_id: groupId,
tag_ids: [urgentTagId],
deadline: "2024-01-20T18:00:00Z" // deadline 설정
})
});
const todo = await response.json();
// ✅ todo.schedules에 자동 생성된 Schedule 포함
console.log(todo.schedules[0]);
// {
// id: "...",
// title: "프로젝트 마감",
// start_time: "2024-01-20T18:00:00Z",
// end_time: "2024-01-20T19:00:00Z", // deadline + 1시간
// source_todo_id: todo.id,
// tags: [{ name: "긴급", ... }] // Todo의 태그가 복사됨
// }
2. 태그 동기화¶
Todo의 태그를 수정하면 연결된 Schedule의 태그도 자동으로 동기화됩니다.
// Todo 태그 수정
await fetch(`/v1/todos/${todoId}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
tag_ids: [newTag1, newTag2] // 태그 변경
})
});
// ✅ 연결된 Schedule의 태그도 자동으로 동기화됨
3. 트리 구조 Todo¶
Todo는 부모-자식 관계를 통해 트리 구조로 구성할 수 있습니다.
// 부모 Todo 생성
const parentTodo = await createTodo({
title: "프로젝트",
tag_group_id: groupId
});
// 자식 Todo 생성
const childTodo = await createTodo({
title: "1단계: 설계",
tag_group_id: groupId,
parent_id: parentTodo.id // 부모 지정
});
4. Schedule에서 Todo 생성¶
기존 Schedule에서 연관된 Todo를 생성할 수 있습니다. 두 가지 방법을 지원합니다.
방법 1: Schedule 생성 시 Todo도 함께 생성¶
// Schedule 생성 시 create_todo_options 옵션 사용
const response = await fetch('/v1/schedules', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
title: "회의 준비",
start_time: "2024-01-20T10:00:00Z",
end_time: "2024-01-20T12:00:00Z",
tag_ids: [tagId],
create_todo_options: {
tag_group_id: groupId // Todo가 속할 그룹 (필수)
}
})
});
const schedule = await response.json();
// ✅ schedule.source_todo_id에 자동 생성된 Todo ID 포함
방법 2: 기존 Schedule에서 Todo 생성 (수동 API)¶
// 기존 Schedule에서 Todo 생성
const response = await fetch(`/v1/schedules/${scheduleId}/todo?tag_group_id=${groupId}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' }
});
const todo = await response.json();
// ✅ Todo 생성됨
// - title, description: Schedule에서 복사
// - deadline: Schedule의 start_time
// - tags: Schedule의 태그가 복사됨
// - status: SCHEDULED
주의
제약사항: 이미 Todo와 연결된 Schedule에서는 다시 Todo를 생성할 수 없습니다 (400 에러).
5. 캘린더 태그 필터링¶
GraphQL Calendar 쿼리에서 태그로 일정을 필터링할 수 있습니다.
// 특정 태그가 있는 일정만 조회
const query = `
query GetFilteredCalendar($startDate: Date!, $endDate: Date!, $tagFilter: TagFilterInput) {
calendar(startDate: $startDate, endDate: $endDate, tagFilter: $tagFilter) {
days {
date
events { id title tags { name } }
}
}
}
`;
const variables = {
startDate: "2024-01-01",
endDate: "2024-01-31",
tagFilter: {
tagIds: [urgentTagId] // "긴급" 태그가 있는 일정만
}
};
TypeScript 타입 정의¶
// ============================================================
// Enums
// ============================================================
export type TodoStatus = "UNSCHEDULED" | "SCHEDULED" | "DONE" | "CANCELLED";
export type ScheduleState = "PLANNED" | "IN_PROGRESS" | "COMPLETED";
// ============================================================
// Tag & TagGroup
// ============================================================
export interface Tag {
id: string;
name: string;
color: string;
description?: string;
group_id: string;
created_at: string;
updated_at: string;
}
export interface TagGroup {
id: string;
name: string;
color: string;
description?: string;
goal_ratios?: Record<string, number>;
is_todo_group: boolean;
created_at: string;
updated_at: string;
tags: Tag[];
}
export interface TagGroupCreate {
name: string;
color: string;
description?: string;
is_todo_group?: boolean;
}
export interface TagCreate {
name: string;
color: string;
description?: string;
group_id: string;
}
// ============================================================
// Schedule
// ============================================================
export interface Schedule {
id: string;
title: string;
description?: string;
start_time: string;
end_time: string;
recurrence_rule?: string;
recurrence_end?: string;
parent_id?: string;
source_todo_id?: string;
state: ScheduleState;
created_at: string;
tags: Tag[];
}
// ============================================================
// Todo
// ============================================================
export interface Todo {
id: string;
title: string;
description?: string;
deadline?: string;
tag_group_id: string;
parent_id?: string;
status: TodoStatus;
created_at: string;
tags: Tag[];
schedules: Schedule[];
}
export interface TodoCreate {
title: string;
description?: string;
tag_group_id: string; // 필수!
tag_ids?: string[];
deadline?: string;
parent_id?: string;
status?: TodoStatus;
}
export interface TodoUpdate {
title?: string;
description?: string;
tag_group_id?: string;
tag_ids?: string[];
deadline?: string;
parent_id?: string;
status?: TodoStatus;
}
// ============================================================
// API Response Types
// ============================================================
export interface TodoStats {
group_id?: string;
total_count: number;
by_tag: TagStat[];
}
export interface TagStat {
tag_id: string;
tag_name: string;
count: number;
}
// ============================================================
// GraphQL Types
// ============================================================
export interface GqlEvent {
id: string;
title: string;
description?: string;
startAt: string;
endAt: string;
createdAt: string;
isRecurring: boolean;
parentId?: string;
instanceStart?: string;
tags: GqlTag[];
}
export interface GqlTag {
id: string;
name: string;
color: string;
description?: string;
groupId: string;
createdAt: string;
updatedAt: string;
}
export interface GqlDay {
date: string;
events: GqlEvent[];
}
export interface GqlCalendar {
days: GqlDay[];
}
export interface TagFilterInput {
tagIds?: string[];
groupIds?: string[];
}
사용 예시¶
전체 워크플로우 예시¶
// 1. 태그 그룹 생성
const groupResponse = await fetch('/v1/tags/groups', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: "업무",
color: "#4A90D9",
is_todo_group: true
})
});
const group = await groupResponse.json();
// 2. 태그 생성
const tagResponse = await fetch('/v1/tags', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: "긴급",
color: "#FF4444",
group_id: group.id
})
});
const tag = await tagResponse.json();
// 3. Todo 생성 (deadline 포함 → Schedule 자동 생성)
const todoResponse = await fetch('/v1/todos', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
title: "프로젝트 마감",
description: "최종 보고서 제출",
tag_group_id: group.id,
tag_ids: [tag.id],
deadline: "2024-01-20T18:00:00Z"
})
});
const todo = await todoResponse.json();
console.log("생성된 Todo:", todo);
console.log("자동 생성된 Schedule:", todo.schedules[0]);
// 4. 캘린더에서 조회 (GraphQL)
const calendarQuery = `
query {
calendar(startDate: "2024-01-01", endDate: "2024-01-31") {
days {
date
events {
id
title
startAt
tags { name color }
}
}
}
}
`;
const calendarResponse = await fetch('/v1/graphql', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query: calendarQuery })
});
const calendarData = await calendarResponse.json();
// 5. 태그로 필터링된 캘린더 조회
const filteredQuery = `
query GetFiltered($tagFilter: TagFilterInput) {
calendar(startDate: "2024-01-01", endDate: "2024-01-31", tagFilter: $tagFilter) {
days {
date
events { id title }
}
}
}
`;
const filteredResponse = await fetch('/v1/graphql', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
query: filteredQuery,
variables: {
tagFilter: { tagIds: [tag.id] }
}
})
});
React Hook 예시¶
import { useState, useEffect } from 'react';
// Todo 목록 조회 훅
function useTodos(groupId?: string) {
const [todos, setTodos] = useState<Todo[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
const fetchTodos = async () => {
try {
const params = groupId ? `?group_ids=${groupId}` : '';
const response = await fetch(`/v1/todos${params}`);
if (!response.ok) throw new Error('Failed to fetch todos');
const data = await response.json();
setTodos(data);
} catch (err) {
setError(err as Error);
} finally {
setLoading(false);
}
};
fetchTodos();
}, [groupId]);
return { todos, loading, error };
}
// Todo 생성 훅
function useCreateTodo() {
const [loading, setLoading] = useState(false);
const createTodo = async (data: TodoCreate): Promise<Todo> => {
setLoading(true);
try {
const response = await fetch('/v1/todos', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail || 'Failed to create todo');
}
return await response.json();
} finally {
setLoading(false);
}
};
return { createTodo, loading };
}
// 사용 예시
function TodoList() {
const { todos, loading } = useTodos();
const { createTodo } = useCreateTodo();
const handleCreate = async () => {
const newTodo = await createTodo({
title: "새 할 일",
tag_group_id: "group-uuid",
deadline: new Date().toISOString()
});
console.log("생성됨:", newTodo);
};
if (loading) return <div>로딩 중...</div>;
return (
<div>
<button onClick={handleCreate}>Todo 추가</button>
{todos.map(todo => (
<div key={todo.id}>
<h3>{todo.title}</h3>
<p>상태: {todo.status}</p>
<p>태그: {todo.tags.map(t => t.name).join(', ')}</p>
{todo.deadline && <p>마감: {todo.deadline}</p>}
</div>
))}
</div>
);
}
주의사항¶
1. tag_group_id는 필수¶
Todo 생성 시 반드시 tag_group_id를 지정해야 합니다.
// ❌ 에러: tag_group_id 누락
await fetch('/v1/todos', {
method: 'POST',
body: JSON.stringify({ title: "할 일" }) // 422 에러 발생
});
// ✅ 올바른 사용
await fetch('/v1/todos', {
method: 'POST',
body: JSON.stringify({
title: "할 일",
tag_group_id: groupId // 필수!
})
});
2. deadline 설정 시 Schedule 자동 생성¶
- Schedule의
start_time은 Todo의deadline과 동일 - Schedule의
end_time은deadline + 1시간으로 자동 설정 - Todo의 태그가 Schedule에 자동으로 복사됨
3. 태그 동기화¶
Todo의 태그를 수정하면 연결된 모든 Schedule의 태그도 함께 업데이트됩니다.
4. 삭제 시 동작¶
- Todo 삭제:
- 해당 Todo에 연결된 Schedule은 함께 삭제
- 자식 Todo는 삭제되지 않고 루트로 승격 (parent_id → NULL)
- 자식 Todo에 연결된 Schedule은 유지됨
- TagGroup 삭제 → 그룹 내 모든 Tag도 함께 삭제 (CASCADE)
5. 날짜/시간 형식¶
모든 datetime 필드는 ISO 8601 형식을 사용합니다.
// ✅ 올바른 형식
"2024-01-20T10:00:00Z" // UTC
"2024-01-20T19:00:00+09:00" // 타임존 포함
// GraphQL Date 필드 (날짜만)
"2024-01-20"
API 요약¶
Todo API¶
| Method | Endpoint | 설명 |
|---|---|---|
| POST | /v1/todos |
Todo 생성 |
| GET | /v1/todos |
Todo 목록 조회 |
| GET | /v1/todos/{id} |
Todo 단건 조회 |
| PATCH | /v1/todos/{id} |
Todo 수정 |
| DELETE | /v1/todos/{id} |
Todo 삭제 |
| GET | /v1/todos/stats |
Todo 통계 조회 |
Tag API¶
| Method | Endpoint | 설명 |
|---|---|---|
| POST | /v1/tags/groups |
태그 그룹 생성 |
| GET | /v1/tags/groups |
태그 그룹 목록 조회 |
| PATCH | /v1/tags/groups/{id} |
태그 그룹 수정 |
| DELETE | /v1/tags/groups/{id} |
태그 그룹 삭제 |
| POST | /v1/tags |
태그 생성 |
| GET | /v1/tags |
태그 목록 조회 |
| PATCH | /v1/tags/{id} |
태그 수정 |
| DELETE | /v1/tags/{id} |
태그 삭제 |
Schedule API (Todo 연동)¶
| Method | Endpoint | 설명 |
|---|---|---|
| POST | /v1/schedules |
Schedule 생성 (create_todo_options로 Todo 동시 생성 가능) |
| POST | /v1/schedules/{id}/todo |
기존 Schedule에서 Todo 생성 |
GraphQL API¶
| Query | 설명 |
|---|---|
calendar(startDate, endDate, tagFilter) |
캘린더 데이터 조회 |
이 가이드를 참고하여 프론트엔드에서 Todo와 관련 기능을 구현하세요!