Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add webform demo app to /examples #437

Merged
merged 8 commits into from
Aug 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions examples/web-form-with-express/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
NOTION_KEY=<your-notion-api-key>
NOTION_PAGE_ID=<notion-page-id>
114 changes: 114 additions & 0 deletions examples/web-form-with-express/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# Notion internal integration full-stack example

## Use web forms to create new databases, pages, page content, and comments

### About the integration

This demo shows how to build an internal integration that allows users to fill out a web form to create new Notion databases, pages, blocks (page content), and comments.

![Database form submitted successfully](./public/assets/home-screen.png)

This demo is referenced in the [Create an integration guide](https://developers.notion.com/docs/create-a-notion-integration) -- an introductory guide to building internal integrations and working with Notion's public API.

The goal of this integration is to show how to build a full-stack app where user interactions on the frontend will trigger public API requests, and, as a result, make the corresponding updates in your Notion workspace.

### File structure

On the frontend, this demo includes:

- `views/index.html`, which represents the app's webpage content. Users will interact with the HTML elements in this page.
- `public/client.js`, the client-side JavaScript added to handle HTML form `submit` events.
- `public/style.css` contains the styles for `views/index.html`.

On the backend, this demo includes:

- `server.js`, which serves `index.html` and defines the endpoints used in the client-side JS code. All Notion public API usage (Notion SDK for JavaScript) is included in this file.

#### Notion endpoints used

This demo includes the following Notion endpoint usage:

- [Create a database](https://developers.notion.com/reference/create-a-database)
- [Create a page](https://developers.notion.com/reference/post-page)
- [Append block children](https://developers.notion.com/reference/patch-block-children)
- [Create a comment](https://developers.notion.com/reference/create-a-comment)

This demo can be expanded further to test other endpoints, as well. For example, you could add a button retrieve all database pages or to delete existing pages.

Some "real-world" applications include expanding this demo to be a blog and using a Notion workspace as a CMS. Additionally, the functionality could also be repurposed to receive app feedback from users.

---

### Running locally

#### 1. Set up your local project

```zsh
# Clone this repository locally
git clone https://github.com/makenotion/notion-sdk-js.git

# Switch into this project
cd notion-sdk-js/examples/web-form-with-express/

# Install the dependencies
npm install
```

#### 2. Set your environment variables in a `.env` file

A `.env.example` file has been included and can be renamed `.env`. Update the environment variables below:

```zsh
NOTION_KEY=<your-notion-api-key>
NOTION_PAGE_ID=<notion-page-id>
```

`NOTION_KEY`: Create a new integration in the [integrations dashboard](https://www.notion.com/my-integrations) and retrieve the API key from the integration's `Secrets` page.

`NOTION_PAGE_ID`: Use the ID of any Notion page you want to add databases to. This page will be the parent of all content created through this integration.

The page ID is the 32 character string at the end of any page URL.
![A Notion page URL with the ID highlighted](./public/assets//page_id.png)

#### 3. Give the integration access to your page

Your Notion integration will need permission to create new databases, etc. To provide access, do the following:

1. Go to the page in your workspace.
2. Click the `•••` (more menu) on the top-right corner of the page.
3. Scroll to the bottom of the menu and click `Add connections`.
4. Search for and select your integration in the `Search for connections...` menu.

Once selected, your integration will have permission to read/write content on the page.

**Note**: For the `Add a comment` form to work, you must give your integration permission to read/write comments. To update integration capabilities, visit the `Capabilities` tab in the [integrations dashboard](https://www.notion.com/my-integrations).

#### 4. Run code

Run the following command:

```zsh
node server.js
```

Check the terminal response to see which port to use when viewing the app locally in your browser of choice (`localhost:<port>`).

Keep the browser console open to see API responses, including errors.

### Using this app

To use this demo app, start by creating a new database via the database form:
![Database form UI](./public/assets/home-screen.png)

The ID of the new database can be used in the next form to create a new page:
![Page form UI](./public/assets/page-form.png)

The blocks and comment forms will accept the page ID that is returned from the page form to create new page content (blocks) and comments.
![Blocks form UI](./public/assets/blocks-form.png)
![Comments form UI](./public/assets/comment-form.png)

If you have the IDs for other databases/pages, you can use them as long as you have shared the target databases/pages with the internal integration.

To learn more about this demo, read the [Create an integration guide](https://developers.notion.com/docs/create-a-notion-integration), which steps through how this code works.

(Thanks to [Glitch](https://glitch.com/) for the starter app used while creating this demo!)
31 changes: 31 additions & 0 deletions examples/web-form-with-express/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"name": "web-form-with-express-example",
"version": "1.0.0",
"description": "Internal Notion intergration demo app using Express.js for a server.",
"main": "server.js",
"scripts": {
"start": "node server.js"
},
"dependencies": {
"@notionhq/client": "^2.2.7",
"dotenv": "^16.3.1",
"express": "^4.18.2"
},
"engines": {
"node": "14.x"
},
"repository": {
"type": "git",
"url": "git+https://github.com/makenotion/notion-sdk-js.git"
},
"author": "Jess Mitchell",
"license": "MIT",
"bugs": {
"url": "https://github.com/makenotion/notion-sdk-js/issues"
},
"keywords": [
"node",
"notion",
"express"
]
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
142 changes: 142 additions & 0 deletions examples/web-form-with-express/public/client.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
// This file is run by the browser each time your view template is loaded

/**
* Define variables that reference elements included in /views/index.html:
*/

// Forms
const dbForm = document.getElementById("databaseForm")
const pageForm = document.getElementById("pageForm")
const blocksForm = document.getElementById("blocksForm")
const commentForm = document.getElementById("commentForm")

// Table cells where API responses will be appended
const dbResponseEl = document.getElementById("dbResponse")
const pageResponseEl = document.getElementById("pageResponse")
const blocksResponseEl = document.getElementById("blocksResponse")
const commentResponseEl = document.getElementById("commentResponse")

/**
* Functions to handle appending new content to /views/index.html
*/

// Appends the API response to the UI
const appendApiResponse = function (apiResponse, el) {
console.log(apiResponse)

// Add success message to UI
const newParagraphSuccessMsg = document.createElement("p")
newParagraphSuccessMsg.innerHTML = "Result: " + apiResponse.message
el.appendChild(newParagraphSuccessMsg)
// See browser console for more information
if (apiResponse.message === "error") return

// Add ID of Notion item (db, page, comment) to UI
const newParagraphId = document.createElement("p")
newParagraphId.innerHTML = "ID: " + apiResponse.data.id
el.appendChild(newParagraphId)

// Add URL of Notion item (db, page) to UI
if (apiResponse.data.url) {
const newAnchorTag = document.createElement("a")
newAnchorTag.setAttribute("href", apiResponse.data.url)
newAnchorTag.innerText = apiResponse.data.url
el.appendChild(newAnchorTag)
}
}

// Appends the blocks API response to the UI
const appendBlocksResponse = function (apiResponse, el) {
console.log(apiResponse)

// Add success message to UI
const newParagraphSuccessMsg = document.createElement("p")
newParagraphSuccessMsg.innerHTML = "Result: " + apiResponse.message
el.appendChild(newParagraphSuccessMsg)

// Add block ID to UI
const newParagraphId = document.createElement("p")
newParagraphId.innerHTML = "ID: " + apiResponse.data.results[0].id
el.appendChild(newParagraphId)
}

/**
* Attach submit event handlers to each form included in /views/index.html
*/

// Attach submit event to each form
dbForm.onsubmit = async function (event) {
event.preventDefault()

const dbName = event.target.dbName.value
const body = JSON.stringify({ dbName })

const newDBResponse = await fetch("/databases", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body,
})
const newDBData = await newDBResponse.json()

appendApiResponse(newDBData, dbResponseEl)
}

pageForm.onsubmit = async function (event) {
event.preventDefault()

const dbID = event.target.newPageDB.value
const pageName = event.target.newPageName.value
const header = event.target.header.value
const body = JSON.stringify({ dbID, pageName, header })

const newPageResponse = await fetch("/pages", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body,
})

const newPageData = await newPageResponse.json()
appendApiResponse(newPageData, pageResponseEl)
}

blocksForm.onsubmit = async function (event) {
event.preventDefault()

const pageID = event.target.pageID.value
const content = event.target.content.value
const body = JSON.stringify({ pageID, content })

const newBlockResponse = await fetch("/blocks", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body,
})

const newBlockData = await newBlockResponse.json()
appendBlocksResponse(newBlockData, blocksResponseEl)
}

commentForm.onsubmit = async function (event) {
event.preventDefault()

const pageID = event.target.pageIDComment.value
const comment = event.target.comment.value
const body = JSON.stringify({ pageID, comment })

const newCommentResponse = await fetch("/comments", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body,
})

const newCommentData = await newCommentResponse.json()
appendApiResponse(newCommentData, commentResponseEl)
}
74 changes: 74 additions & 0 deletions examples/web-form-with-express/public/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/* this file is loaded by index.html and styles the page */

* {
box-sizing: border-box;
}

body {
font-family: sans-serif;
margin: 2em 1em;
line-height: 1.5em;
}

h1 {
color: #7171d1;
max-width: calc(100% - 5rem);
line-height: 1.1;
}

table {
width: 100%;
}

th {
text-align: left;
}

td {
width: 50%;
word-break: break-all;
}

form {
background-color: #eee;
display: grid;
grid-gap: 1em;
padding: 1em;
max-width: 40ch;
}

input {
border: 1px solid silver;
display: block;
font-size: 16px;
margin-bottom: 10px;
padding: 5px;
width: 100%;
}

form input[type="submit"] {
background-color: #bbbbf2;
border: 2px solid currentColor;
border-radius: 0.25em;
cursor: pointer;
font-size: inherit;
line-height: 1.4em;
padding: 0.25em 1em;
max-width: 20ch;
cursor: pointer;
margin-left: auto;
}

form input[type="submit"]:hover {
background-color: #9292d3;
}

form input[type="submit"]:active {
background-color: #b2b2d5;
}

footer {
margin-top: 3em;
padding-top: 1.5em;
border-top: 1px solid lightgrey;
}
Loading
Loading