Page query in database
Peponi │ 1/23/2025 │ 14m
C#
NotionDatabaseHttpPostGetPatchJson
Page query in database
1/23/2025
14m
Peponi
C#
NotionDatabaseHttpPostGetPatchJson
1. Introduction
Notion은 페이지, 데이터베이스 등에 대해 REST API를 지원한다.
- API 접근을 하려면 Database 및 API 키가 필요하다.
키를 얻는 방법에 대해서는 Build your first integration을 참조한다. - Notion의 API Reference는 NOTION API - Introduction을 참조한다.
여기서는 database에 대한 page 작업을 다룬다.
2. Database to perform
Page query 방법에 앞서, 테스트를 수행할 DB의 형태는 아래와 같다.
DB 작업 수행 간 JSON 형식의 Page object를 HTTP body에 실어 전송받게 된다. Reference 페이지를 참조하여 구현한 JSON object의 형태는 아래와 같다.
QueryPageItem (펼치기 / 접기)
using System.Text.Json.Serialization;
namespace NotionAPI.Objects;
// https://developers.notion.com/reference/post-page 에 따라 작성
public class QueryPageItem
{
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public Parent? parent { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public Dictionary<string, PageProperty>? properties { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public List<object>? children { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public object? icon { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public object? cover { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public bool? archived { get; set; }
}
PageInformation (펼치기 / 접기)
using System.Text.Json.Serialization;
namespace NotionAPI.Objects;
// https://developers.notion.com/reference/page 에 따라 작성
public class PageInformation
{
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? @object { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? id { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public DateTime created_time { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public User? created_by { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public DateTime last_edited_time { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public User? last_edited_by { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public bool archived { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public object? icon { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public object? cover { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public Dictionary<string, PageProperty>? properties { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public Parent? parent { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? url { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? public_url { get; set; }
}
PageProperty (펼치기 / 접기)
using System.Text.Json;
using System.Text.Json.Serialization;
namespace NotionAPI.Objects;
// https://developers.notion.com/reference/page-property-values 에 따라 작성
[JsonConverter(typeof(PagePropertyConverter))]
public class PageProperty
{
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? id { get; set; }
}
public sealed class PageSelect : PageProperty
{
public Select? select { get; set; }
}
public sealed class PageTitle : PageProperty
{
public List<RichText>? title { get; set; }
}
public class PagePropertyConverter : JsonConverter<PageProperty>
{
public override PageProperty? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var jsonDoc = JsonDocument.ParseValue(ref reader);
if (jsonDoc.RootElement.TryGetProperty("type", out var typeName))
{
return typeName.GetString() switch
{
"select" => JsonSerializer.Deserialize<PageSelect>(jsonDoc),
"title" => JsonSerializer.Deserialize<PageTitle>(jsonDoc),
_ => null
};
}
return null;
}
public override void Write(Utf8JsonWriter writer, PageProperty value, JsonSerializerOptions options)
{
switch (value)
{
case PageSelect select:
JsonSerializer.Serialize(writer, select);
break;
case PageTitle title:
JsonSerializer.Serialize(writer, title);
break;
}
}
}
User (펼치기 / 접기)
using System.Text.Json.Serialization;
namespace NotionAPI.Objects;
// https://developers.notion.com/reference/user 에 따라 작성
[JsonPolymorphic(TypeDiscriminatorPropertyName = "type")]
[JsonDerivedType(typeof(People), typeDiscriminator: "person")]
[JsonDerivedType(typeof(Bots), typeDiscriminator: "bot")]
public class User
{
public string? @object { get; set; }
public string? id { get; set; }
public string? name { get; set; }
public string? avatar_url { get; set; }
}
public sealed class People : User
{
public object? person { get; set; }
}
public sealed class Bots : User
{
public object? bot { get; set; }
}
Parent (펼치기 / 접기)
using System.Text.Json.Serialization;
namespace NotionAPI.Objects;
// https://developers.notion.com/reference/parent-object 에 따라 작성
[JsonPolymorphic(TypeDiscriminatorPropertyName = "type")]
[JsonDerivedType(typeof(DatabaseParent), typeDiscriminator: "database_id")]
[JsonDerivedType(typeof(PageParent), typeDiscriminator: "page_id")]
[JsonDerivedType(typeof(WorkspaceParent), typeDiscriminator: "workspace")]
[JsonDerivedType(typeof(BlockParent), typeDiscriminator: "block_id")]
public class Parent
{
}
public sealed class DatabaseParent : Parent
{
public string? database_id { get; set; }
}
public sealed class PageParent : Parent
{
public string? page_id { get; set; }
}
public sealed class WorkspaceParent : Parent
{
public bool workspace { get; set; } = true;
}
public sealed class BlockParent : Parent
{
public string? block_id { get; set; }
}
RichText (펼치기 / 접기)
using System.Text.Json.Serialization;
namespace NotionAPI.Objects;
// https://developers.notion.com/reference/rich-text 에 따라 작성
[JsonPolymorphic(TypeDiscriminatorPropertyName = "type")]
[JsonDerivedType(typeof(RichTextWithText), typeDiscriminator: "text")]
[JsonDerivedType(typeof(RichTextWithMention), typeDiscriminator: "mention")]
[JsonDerivedType(typeof(RichTextWithEquation), typeDiscriminator: "equation")]
public class RichText
{
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public Annotations? annotations { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? plain_text { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? href { get; set; }
}
public sealed class RichTextWithText : RichText
{
public Text? text { get; set; }
}
public sealed class RichTextWithMention : RichText
{
public object? mention { get; set; }
}
public sealed class RichTextWithEquation : RichText
{
public object? equation { get; set; }
}
public class Annotations
{
public bool bold { get; set; }
public bool italic { get; set; }
public bool strikethrough { get; set; }
public bool underline { get; set; }
public bool code { get; set; }
public string? color { get; set; } = "default";
}
public class Text
{
public string? content { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public Link? link { get; set; }
}
public class Link
{
public string? url { get; set; }
}
Select (펼치기 / 접기)
using System.Text.Json.Serialization;
namespace NotionAPI.Objects;
// https://developers.notion.com/reference/property-object#select 에 따라 작성
public class Select
{
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? id { get; set; }
public string? name { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? color { get; set; }
}
3. Create a page
DB에 새 페이지를 추가하는 방법은 아래와 같다.
using NotionAPI.Objects;
using System.Net.Http.Json;
using System.Text.Json;
private static void Main(string[] args)
{
string baseUri = $"https://api.notion.com/v1/pages";
string databaseKey = "데이터베이스 키";
string APIKey = "API 키";
Create(baseUri, databaseKey, APIKey);
}
private static bool Create(string baseUri, string databaseKey, string APIKey)
{
HttpClient client = new();
// https://developers.notion.com/reference/post-page 문서 내용에 따라 Post로 request 작성
var request = new HttpRequestMessage(HttpMethod.Post, $"{baseUri}");
request.Headers.Add("Authorization", $"Bearer {APIKey}");
request.Headers.Add("Notion-Version", "2022-06-28");
var queryItem = new QueryPageItem();
queryItem.parent = new DatabaseParent() { database_id = databaseKey };
var properties = new Dictionary<string, PageProperty>
{
{ "이름", new PageTitle() { title = [new RichTextWithText() { text = new() { content = "5" } }] } },
{ "선택", new PageSelect() { select = new() { name = "2" } } }
};
queryItem.properties = properties;
request.Content = JsonContent.Create(queryItem);
var response = client.Send(request);
// StatusCode를 포함한 Header 출력
Console.WriteLine(response);
// JSON 형식의 Body 출력
var content = new StreamReader(response.Content.ReadAsStream()).ReadToEnd();
Console.WriteLine(content);
// Parsing
var parsed = JsonSerializer.Deserialize<PageInformation>(content);
Console.WriteLine($"Page key : {parsed?.id}");
return response.StatusCode == System.Net.HttpStatusCode.OK;
}
정상적으로 페이지가 추가된 경우 아래 이미지와 같이 UI에 표시된다.
수신되는 데이터는 아래와 같다.
Header (펼치기 / 접기)
StatusCode: 200, ReasonPhrase: 'OK', Version: 1.1, Content: System.Net.Http.HttpConnectionResponseContent, Headers:
{
Date: Thu, 11 Apr 2024 23:03:08 GMT
Connection: keep-alive
X-Powered-By: Express
x-notion-request-id: 79425409-d6c4-4f7e-af46-c4fcf9b1d7fd
ETag: W/"3a1-FH+l03dxu3kIA70Ha6M4x4zVZ1Q"
Vary: Accept-Encoding
CF-Cache-Status: DYNAMIC
Set-Cookie: __cf_bm=db7NUVgzSpxFIGnabQKSx8uKGVWP4sdzbSIrAgEpUMc-1712876588-1.0.1.1-ncsWrdfTHvjv7PNpeP4jU9BKfgwbbxk4PwOKWWn8er1QkthqzlKeEJMr_PVJET.H2d49DLFKaDG680qkRsS1jg; path=/; expires=Thu, 11-Apr-24 23:33:08 GMT; domain=.notion.com; HttpOnly; Secure
Server: cloudflare
CF-RAY: 872e9bb22f71329c-ICN
Content-Type: application/json; charset=utf-8
Content-Length: 929
}
Body (펼치기 / 접기)
{
"object": "page",
"id": "Page id",
"created_time": "2024-04-11T23:03:00.000Z",
"last_edited_time": "2024-04-11T23:03:00.000Z",
"created_by": {
"object": "user",
"id": "유저 id"
},
"last_edited_by": {
"object": "user",
"id": "유저 id"
},
"cover": null,
"icon": null,
"parent": {
"type": "database_id",
"database_id": "DB id"
},
"archived": false,
"in_trash": false,
"properties": {
"선택": {
"id": "sKnP",
"type": "select",
"select": {
"id": "CsLi",
"name": "2",
"color": "gray"
}
},
"이름": {
"id": "title",
"type": "title",
"title": [
{
"type": "text",
"text": {
"content": "5",
"link": null
},
"annotations": {
"bold": false,
"italic": false,
"strikethrough": false,
"underline": false,
"code": false,
"color": "default"
},
"plain_text": "5",
"href": null
}
]
}
},
"url": "https://www.notion.so/5-Page id",
"public_url": null,
"request_id": "79425409-d6c4-4f7e-af46-c4fcf9b1d7fd"
}
4. Retrieve a page
Page의 id를 알고 있다면 해당 page의 정보를 얻어올 수 있다.
using NotionAPI.Objects;
using System.Text.Json;
private static void Main(string[] args)
{
string baseUri = $"https://api.notion.com/v1/pages";
string pageKey = "Page 키";
string APIKey = "API 키";
Console.WriteLine(Retrieve(baseUri, pageKey, APIKey));
}
private static bool Retrieve(string baseUri, string pageKey, string APIKey)
{
HttpClient client = new();
// https://developers.notion.com/reference/retrieve-a-page 문서 내용에 따라 Get으로 request 작성
var request = new HttpRequestMessage(HttpMethod.Get, $"{baseUri}/{pageKey}");
request.Headers.Add("Authorization", $"Bearer {APIKey}");
request.Headers.Add("Notion-Version", "2022-06-28");
var response = client.Send(request);
// StatusCode를 포함한 Header 출력
Console.WriteLine(response);
// JSON 형식의 Body 출력
var content = new StreamReader(response.Content.ReadAsStream()).ReadToEnd();
Console.WriteLine(content);
var parsed = JsonSerializer.Deserialize<PageInformation>(content);
return response.StatusCode == System.Net.HttpStatusCode.OK;
}
수신되는 데이터는 아래와 같다.
Header (펼치기 / 접기)
StatusCode: 200, ReasonPhrase: 'OK', Version: 1.1, Content: System.Net.Http.HttpConnectionResponseContent, Headers:
{
Date: Thu, 11 Apr 2024 23:09:09 GMT
Connection: keep-alive
X-Powered-By: Express
x-notion-request-id: b3ca9a32-2a2d-4e98-9198-a97d71c82434
ETag: W/"3a1-VH41nGESWpRWFLu2jXoGdY84fSU"
Vary: Accept-Encoding
CF-Cache-Status: DYNAMIC
Set-Cookie: __cf_bm=JEsgOya342GIBSYbtCzwFBr7m.gOIiJF4w5rrrvnNnI-1712876949-1.0.1.1-aJ2d3dMM99HohpAwBh1A99GRK61AnfsFJ1nCeYw66lEEGqXVROXceGAWwl3GqZaseV0ghn0wSd.iQ8QputNZyg; path=/; expires=Thu, 11-Apr-24 23:39:09 GMT; domain=.notion.com; HttpOnly; Secure
Server: cloudflare
CF-RAY: 872ea4833956c071-ICN
Content-Type: application/json; charset=utf-8
Content-Length: 929
}
Body (펼치기 / 접기)
{
"object": "page",
"id": "Page id",
"created_time": "2024-04-11T23:03:00.000Z",
"last_edited_time": "2024-04-11T23:03:00.000Z",
"created_by": {
"object": "user",
"id": "유저 id"
},
"last_edited_by": {
"object": "user",
"id": "유저 id"
},
"cover": null,
"icon": null,
"parent": {
"type": "database_id",
"database_id": "DB id"
},
"archived": false,
"in_trash": false,
"properties": {
"선택": {
"id": "sKnP",
"type": "select",
"select": {
"id": "CsLi",
"name": "2",
"color": "gray"
}
},
"이름": {
"id": "title",
"type": "title",
"title": [
{
"type": "text",
"text": {
"content": "5",
"link": null
},
"annotations": {
"bold": false,
"italic": false,
"strikethrough": false,
"underline": false,
"code": false,
"color": "default"
},
"plain_text": "5",
"href": null
}
]
}
},
"url": "https://www.notion.so/5-Page id",
"public_url": null,
"request_id": "b3ca9a32-2a2d-4e98-9198-a97d71c82434"
}
5. Update page properties
PATCH
request를 통해 page의 속성을 변경할 수 있다. 이 때 변경할 속성을 body에 실어 보낸다.
using NotionAPI.Objects;
using System.Net.Http.Json;
using System.Text.Json;
private static void Main(string[] args)
{
string baseUri = $"https://api.notion.com/v1/pages";
string pageKey = "Page 키";
string APIKey = "API 키";
Update(baseUri, pageKey, APIKey);
}
private static bool Update(string baseUri, string pageKey, string APIKey)
{
HttpClient client = new();
// https://developers.notion.com/reference/patch-page 문서 내용에 따라 Patch로 request 작성
var request = new HttpRequestMessage(HttpMethod.Patch, $"{baseUri}/{pageKey}");
request.Headers.Add("Authorization", $"Bearer {APIKey}");
request.Headers.Add("Notion-Version", "2022-06-28");
var queryItem = new QueryPageItem();
var properties = new Dictionary<string, PageProperty>()
{
{ "선택" , new PageSelect() { select = new() { name = "1" } } }
};
queryItem.properties = properties;
request.Content = JsonContent.Create(queryItem);
var response = client.Send(request);
// StatusCode를 포함한 Header 출력
Console.WriteLine(response);
// JSON 형식의 Body 출력
var content = new StreamReader(response.Content.ReadAsStream()).ReadToEnd();
Console.WriteLine(content);
var parsed = JsonSerializer.Deserialize<PageInformation>(content);
return response.StatusCode == System.Net.HttpStatusCode.OK;
}
정상적으로 업데이트가 수행되면 아래 이미지와 같이 UI에 표시된다.
수신되는 데이터는 아래와 같다.
Header (펼치기 / 접기)
StatusCode: 200, ReasonPhrase: 'OK', Version: 1.1, Content: System.Net.Http.HttpConnectionResponseContent, Headers:
{
Date: Thu, 11 Apr 2024 23:25:12 GMT
Connection: keep-alive
X-Powered-By: Express
x-notion-request-id: e9860a05-57ad-4af3-840a-9131d1a63a14
ETag: W/"3a3-1n/Ro8ikDAsBoSwv2px7Hn0qHOM"
Vary: Accept-Encoding
CF-Cache-Status: DYNAMIC
Set-Cookie: __cf_bm=CPbn0HafswMCKjwPy0KcFkRS9acC84..kK1qKaFfMDA-1712877912-1.0.1.1-nts9GcwFNiFeWgx6.__DrUgd2QsvGXSsA4r2oivFQd5GZcHQcCWcj.8loYqKvG.JL3xxdUUXpuV4FKbWfgac4w; path=/; expires=Thu, 11-Apr-24 23:55:12 GMT; domain=.notion.com; HttpOnly; Secure
Server: cloudflare
CF-RAY: 872ebc081c9bc17d-ICN
Content-Type: application/json; charset=utf-8
Content-Length: 931
}
Body (펼치기 / 접기)
{
"object": "page",
"id": "Page id",
"created_time": "2024-04-11T23:03:00.000Z",
"last_edited_time": "2024-04-11T23:25:00.000Z",
"created_by": {
"object": "user",
"id": "유저 id"
},
"last_edited_by": {
"object": "user",
"id": "유저 id"
},
"cover": null,
"icon": null,
"parent": {
"type": "database_id",
"database_id": "DB id"
},
"archived": false,
"in_trash": false,
"properties": {
"선택": {
"id": "sKnP",
"type": "select",
"select": {
"id": "@:oT",
"name": "1",
"color": "yellow"
}
},
"이름": {
"id": "title",
"type": "title",
"title": [
{
"type": "text",
"text": {
"content": "5",
"link": null
},
"annotations": {
"bold": false,
"italic": false,
"strikethrough": false,
"underline": false,
"code": false,
"color": "default"
},
"plain_text": "5",
"href": null
}
]
}
},
"url": "https://www.notion.so/5-Page id",
"public_url": null,
"request_id": "e9860a05-57ad-4af3-840a-9131d1a63a14"
}
6. Archive a page
Notion API는 page의 영구 삭제를 지원하지 않는 대신 보관 기능을 지원하며 UI에서 숨김 처리가 된다. Body의 archived
속성을 true
로 설정하면 보관, false
로 설정하면 해제가 된다. 아래는 true
로 설정하여 보관하는 예시를 보여준다.
using NotionAPI.Objects;
using System.Net.Http.Json;
private static void Main(string[] args)
{
string baseUri = $"https://api.notion.com/v1/pages";
string pageKey = "Page 키";
string APIKey = "API 키";
Archieve(baseUri, pageKey, APIKey);
}
private static bool Archieve(string baseUri, string pageKey, string APIKey)
{
HttpClient client = new();
// https://developers.notion.com/reference/archive-a-page 문서 내용에 따라 Patch로 request 작성
var request = new HttpRequestMessage(HttpMethod.Patch, $"{baseUri}/{pageKey}");
request.Headers.Add("Authorization", $"Bearer {APIKey}");
request.Headers.Add("Notion-Version", "2022-06-28");
var queryItem = new QueryPageItem();
queryItem.archived = true;
request.Content = JsonContent.Create(queryItem);
var response = client.Send(request);
// StatusCode를 포함한 Header 출력
Console.WriteLine(response);
// JSON 형식의 Body 출력
Console.WriteLine(new StreamReader(response.Content.ReadAsStream()).ReadToEnd());
return response.StatusCode == System.Net.HttpStatusCode.OK;
}
정상적으로 기능이 수행되었다면 UI에서 항목이 사라진다.
수신되는 데이터는 아래와 같다.
Header (펼치기 / 접기)
StatusCode: 200, ReasonPhrase: 'OK', Version: 1.1, Content: System.Net.Http.HttpConnectionResponseContent, Headers:
{
Date: Thu, 11 Apr 2024 23:33:54 GMT
Connection: keep-alive
X-Powered-By: Express
x-notion-request-id: 121f6b11-1c42-4f4f-9171-1eea30a2c5ac
ETag: W/"3a1-TL1xmU1SdWnUsVY+g5AIC/QdNmU"
Vary: Accept-Encoding
CF-Cache-Status: DYNAMIC
Set-Cookie: __cf_bm=_lT094_241halWvRUhfWo9HJyKUIzPo9HezNiGr1kfo-1712878434-1.0.1.1-qgv6Tzj9Whr9x7dXz7M53J3dkIU3VDiSMeHwfTScycdRndYTxuG0Bq9Q.gqxNVpqA1Yf_puzTUvDf6rzSNqn8w; path=/; expires=Fri, 12-Apr-24 00:03:54 GMT; domain=.notion.com; HttpOnly; Secure
Server: cloudflare
CF-RAY: 872ec8c8d89ac067-ICN
Content-Type: application/json; charset=utf-8
Content-Length: 929
}
Body (펼치기 / 접기)
{
"object": "page",
"id": "Page id",
"created_time": "2024-04-11T23:03:00.000Z",
"last_edited_time": "2024-04-11T23:33:00.000Z",
"created_by": {
"object": "user",
"id": "유저 id"
},
"last_edited_by": {
"object": "user",
"id": "유저 id"
},
"cover": null,
"icon": null,
"parent": {
"type": "database_id",
"database_id": "DB id"
},
"archived": true,
"in_trash": true,
"properties": {
"선택": {
"id": "sKnP",
"type": "select",
"select": {
"id": "@:oT",
"name": "1",
"color": "yellow"
}
},
"이름": {
"id": "title",
"type": "title",
"title": [
{
"type": "text",
"text": {
"content": "5",
"link": null
},
"annotations": {
"bold": false,
"italic": false,
"strikethrough": false,
"underline": false,
"code": false,
"color": "default"
},
"plain_text": "5",
"href": null
}
]
}
},
"url": "https://www.notion.so/5-Page id",
"public_url": null,
"request_id": "121f6b11-1c42-4f4f-9171-1eea30a2c5ac"
}
7. 참조 자료
- NOTION API - Introduction (Notion developers)
- Build your first integration (Notion developers)
- Create a page (Notion developers)
- Retrieve a page (Notion developers)
- Update page properties (Notion developers)
- Archive a page (Notion developers)
- System.Text.Json을 사용하여 파생 클래스의 속성을 직렬화하는 방법 (.NET Standard)
- System.Text.Json을 사용하여 속성을 무시하는 방법 (.NET Standard)
- .NET에서 JSON serialization(마샬링)용 사용자 지정 변환기를 작성하는 방법 (.NET Standard)