Learn how to use the new Notion API with Node.js and FastifyJS Notion is productivity software that allows you to create systems for knowledge management, project management, and note-taking. They recently released their API to the public. You can use the API to integrate your Notion data to any application you want. Thus, this article shows you to: build a Node.js API with Notion use FastifyJS to create the API Without further ado, let's start configuring your Notion workspace. Create integrations in Notion Go to your and create a new integration by clicking on the "+ New Integration" button. After that, you will be prompted to fill three fields: Notion integrations the name of your project a logo for the project choose an associated workspace Figure 1 above illustrates the setup process. After filling all the fields, click on the "Submit" button to create the integration. Once you click the submit button, your integration should be created. You will be redirected to another page, from where you can grab your " ". Figure 2 shows that. Internal Integration Token Before going further, it's important to note that you need to leave " " checked, as you can see in figure 2. Internal integration For a tutorial about creating Notion integrations, I recommend checking their . more in-depth official documentation Share your Notion pages The last step is to share whatever page you wish with the newly-created integration. Go to your Notion account, and choose any page you want. After the page opens, click on the " " button, and then click on the field saying " ". Share Add people, emails, groups, or integrations Figure 3, below, illustrates that. After clicking on that field, you are prompted to choose with whom you want to share the page. Choose your integration. In my case, I share the "My space" page with my integration called "My notion space". That's all you need to do when it comes to configuring Notion, so well done! You are done with the Notion configuration, and you are ready to use the Notion API. : If you want to use the same document as me, you can duplicate this . After you duplicate it, share it with your integration ( ) and use your database ID ( ). Note job applications template see the process above you'll see how to get the database ID later Configure the Node.js project You need to use the Notion SDK for JavaScript to interact with the Notion API in your Node.js application. The Notion SDK is a client for the Notion API, and you can find more about it on its . GitHub repository The first step is to create the application structure. Create a new folder and initialize the application as follows: mkdir notion-api-app cd notion-api-app npm init -y The command npm creates the package.json file and initialises it with the default values. If you want to fill the fields manually, remove the -y flag and follow the instructions. init -y The next step is to install the packages required to create the application. You'll use the following packages: - to interact with the Notion API. Notion SDK - to build the Node.js API. FastifyJS - to manage environment variables. dotenv Thus, you can install the packages as follows: npm install @notionhq/client fastify dotenv Once the packages are installed, you need to create a file to store the environmental variables. You will store the Notion secret token and the database ID in the file. Thus, create the file as follows: .env .env touch .env Now go to and click on your integration. You should see a field called (see figure 2 for reference). Click on the "Show" button and copy the token. your Notion integrations Internal Integration Token The next step is to go to your .env file and add the token: NOTION_TOKEN= < > your_token : Replace with the token you copied earlier. Note <your_token> Before going further, you also need to create the folder structure for the application. You need to create the following folders: - the home for your server configuration app.js - it hosts all the other folders. src - the home folder for your routes. routes - the Notion client resides here. notion - the home folder for your business logic. controllers You can create all the folders and files from the command line as follows: touch app.js mkdir src mkdir src/routes src/notion src/controllers touch src/routes/routes.js touch src/notion/client.js touch src/controllers/controllers.js This is how the structure of your application should look like until this point: Now you are ready to build the application! Configure Notion in the application It's time to configure the Notion client so you can use it to manipulate data. Go into the following file and add the following code: src/notion/client.js { Client } = ( ); ( ).config(); const require '@notionhq/client' require 'dotenv' With the first line, you import the Notion client, and with the second one, you import the package. The purpose of these imports is to allow you to use the Notion client and the environmental variables. dotenv The next step is to create a new Notion client. In the same file, after the imports, write the following code: notion = Client({ : process.env.NOTION_TOKEN }); const new auth With the above code, you create a new instance of the Notion client and pass your secret token. Lastly, you store it in the constant so you can re-use the client. notion Before going further, you need to export the notion constant so you can use it when you write the business logic. In the same file - -, in the end, write the following code: src/notion/client.js .exports = { notion }; module Now you are ready to use the Notion client. Check to see how your file client.js file should look up to this point. this gist How to get the database ID There is one more step before building the routes. You need the database ID so you can perform operations such as: creating, reading and modifying data You can get the database ID in two ways. You can either or . do it from the browser programmatically If you prefer to do it from the browser, open the Notion document in your browser and copy the ID as shown in figure 5. Alternatively, you can get the database ID programmatically, as shown in the snippet below: getDatabases = () => { databases = notion.databases.list(); .log(databases); }; const async const await console The above code snippet lists all your databases and the information about them. For a visual representation, see figure 6 below. Whatever option you end up using, copy the database ID and add it to your file. At this point, your file should look as follows: .env .env NOTION_TOKEN= <your_DB_ID> DB_ID= < > your_token You finished configuring the Notion client in your project. The next step is to create the routes and business logic. Add FastifyJS Open the file and import FastifyJS at the top of the file. app.js app = ( )({ : }); const require 'fastify' logger true After that, import the "dotenv" package again so you can use the port, which is defined in the file. .env ( ).config(); require 'dotenv' The next step is to register the routes, even though you did not write them yet. After the imports, add the following line: app.register( ( )); require './src/routes/routes' With the above line, you register all the routes. That is, you make the routes available so users can access them. Lastly, listen to a port so you can run the application. Add the following code at the bottom of the file: app.listen(process.env.PORT, (err, addr) => { (err) { app.log.error(err); process.exit( ); } app.log.info( ); }); if 1 `Your server is listening on port 🧨` ${process.env.PORT} The application listens to a port you define in the file. If there is an error, it logs the error. Otherwise, it outputs in the console that the server is running and on which port it is running. .env Thus, add the port number - 3333 - in your file. This port number is just an example; you can use any port number you want. By this point, your file should look as follows: .env .env NOTION_TOKEN= <your_DB_ID> PORT=3333 DB_ID= < > your_token Check this gist to see how your file app.js file should look up to this point. Build the routes The next step is to create the routes. Their purpose is to allow people to perform CRUD operations ( ). except deleting data in this tutorial For the beginning, go to and import the controller at the top of the file. src/routes/routes.js controller = ( ); const require '../controllers/controllers' Even though you did not write the business logic yet, you will need it. After that, create the function and export it: routes { }; .exports = routes; async ( ) function routes app, opts // the code for routes go here module In the routes method, you configure your application's endpoints. For each route, you specify, at minimum, the: - for instance, you use the HTTP verb to return all items in your database. HTTP method GET - for example, you might use the endpoint to return all the job applications. URL /job/applications - this represents the business logic. For example, what happens when people access the endpoint . handler /job/applications Now that you understand how routes work in FastifyJS, let's build the first route. Show all job applications The first route shows all job applications when a user makes a request to the endpoint . GET /job/applications app.route({ : , : , : controller.getAllApplications }); method 'GET' url '/job/applications' handler Get a specific job application The second route shows only one job application. When the user makes a request to the route, it shows only the job application whose ID matches the provided ID in the URL. GET /job/applications/:id Write the following code after the first route: app.route({ : , : , : controller.getApplication }); method 'GET' url '/job/applications/:id' handler Filter job applications The third route allows the users to filter the job applications by the company name. The user can use a query parameter called to filter the results. company An example of a URL would be . Thus, by making a request to that URL, the application returns only the job application for the company "CatalinSoft". /job/applications/filter?company="CatalinSoft" GET Write the following code in : routes.js app.route({ : , : , : controller.filterApplications }); method 'GET' url '/job/applications/filter' handler Add a new job application The fourth route allows people to make a request to add job applications into the document. When you make a POST request to the endpoint ' ', with the appropriate body, it adds the job application to the Notion document. POST /job/applications Write the following code: app.route({ : , : , : controller.addApplication }); method 'POST' url '/job/applications' handler Update existing application Lastly, you have the route which allows people to update an existing job application. Make a request to with the appropriate body, and you will update the record. PATCH /job/applications/:id app.route({ : , : , : controller.updateApplication }); method 'PATCH' url '/job/applications/:id' handler Those are all the routes. As you can see, they follow the same pattern: You specify the HTTP method. You add the URL where people can make the request. You provide the handler which is triggered when a request is made to that route. Also, you might've noticed that there is no "delete" route. That is because the Notion API does not allow you to delete resources at the moment. Check to see the complete routes.js file. this gist Add the business logic The last step of the tutorial is to build the business logic. The business logic specifies what happens when a certain HTTP request is made to a specific endpoint. Let's start by importing the Notion client in the file. Open the file and add the following line at the top of the document: controllers.js { notion } = ( ); const require '../notion/client' Now, you can use the client to manipulate data. Retrieve all job applications The first method you write is the one that returns all the job applications. Write the following code in : controllers.js getAllApplications = (req, res) => { data = notion.databases.query({ : process.env.DB_ID }); }; const async const await database_id Up to this point, you use the Notion client to query your database. The above piece of code returns an object, which in turn contains an array with all the pages from your database. This is the result you get if you run the above code: { : , : [ { : , : , : , : , : [ ], : , : [ ] }, { : , : , : , : , : [ ], : , : [ ] } ], : , : } object 'list' results object 'page' id '8e8e8f35-09ac-40fd-92a2-a3743b271e2e' created_time '2021-05-26T05:45:00.000Z' last_edited_time '2021-05-26T05:45:00.000Z' parent Object archived false properties Object object 'page' id '03a7aea1-c58b-49ce-9843-87131c37192a' created_time '2021-05-25T06:10:08.816Z' last_edited_time '2021-05-25T06:10:08.816Z' parent Object archived false properties Object next_cursor null has_more false From this response, the field is the one that interests you. That's the array with all the pages from your database. results Since you have an array, you need to loop over it and return only the pages. Go back to the method and add the rest of the code: getAllApplications getAllApplications = (req, res) => { data = notion.databases.query({ : process.env.DB_ID }); pages = data.results.map( { { : page.id, : page.created_time, : page.last_edited_time, : page.properties.Company.title[ ].plain_text, : page.properties.Position.select.name, : page.properties.Status.select.name, : page.properties[ ].date.start, : page.properties[ ].url, : page.properties.Comments.rich_text[ ].plain_text } }); pages; }; const async const await database_id const => page return id created updated company 0 position status deadline 'Next Deadline' jobDescription 'Job Description' comments 0 return In the second part of the code, you map over the array and return an array of custom objects. These custom objects contain the following information: results the ID when it was created when it was last updated the company name the position you applied for the status of the application the application deadline the job description additional comments Let's make a GET request to and see what happens. localhost:3333/job/applications Figure 7 illustrates what happens after making the request. You can see it returns an array of objects, exactly how you structured them in the method . getAllApplications Get a specific application The next step is to add the business logic for retrieving only a specific job application. This time, you use different methods. Rather than querying a database, you retrieve a specific page. Write the following method in your file : controller.js getApplication = (req, res) => { page = notion.pages.retrieve({ : req.params.id }); { : page.id, : page.created_time, : page.last_edited_time, : page.properties.Company.title[ ].plain_text, : page.properties.Position.select.name, : page.properties.Status.select.name, : page.properties[ ].date.start, : page.properties[ ].url, : page.properties.Comments.rich_text[ ].plain_text }; }; const async const await page_id return id created updated company 0 position status deadline 'Next Deadline' jobDescription 'Job Description' comments 0 The method takes the ID of the page from the URL. Thus, if you make a request to the URL , the method takes the page ID from it. GET http://localhost:3333/job/applications/8e8e8f35-09ac-40fd-92a2-a3743b271e2e After that, it returns the page whose ID matches the ID provided in the URL. Let's make a request and see what happens. Figure 8 illustrates that it returns the custom page object you defined in the method . getApplication Filter applications The Notion client allows you to filter the data as well. For example, you might want to filter the results by the company name. Thus, you can pass the company name in the URL, and it will only return the job applications for that company. Write the following code in : controllers.js filterApplications = (req, res) => { data = notion.databases.query({ : process.env.DB_ID, : { : , : { : req.query.company || } } }); pages = data.results.map( { { : page.id, : page.created_time, : page.last_edited_time, : page.properties.Company.title[ ].plain_text, : page.properties.Position.select.name, : page.properties.Status.select.name, : page.properties[ ].date.start, : page.properties[ ].url, : page.properties.Comments.rich_text[ ].plain_text } }); pages; }; const async const await database_id filter property 'Company' text contains '' const => page return id created updated company 0 position status deadline 'Next Deadline' jobDescription 'Job Description' comments 0 return This method is similar to the first method - . The only difference is that you also pass the property when you query the Notion database. getAllApplications filter Querying the database returns a list object that also contains an array called . The array contains all the matched records. Thus, you map over the array and return an array with custom objects. results results results As mentioned previously, you filter the results by the company name. If you make a request to the URL , you will only get the Notion job applications. GET http://localhost:3333/job/applications/filter?company=Notion Figure 9 illustrates a request to . You can see that it return only the records that match . That is all the Notion job applications. GET http://localhost:3333/job/applications/filter?company=Notion ?company=Notion Add a new application So far, you can: retrieve and display all the job applications access a specific job application filter data by the company name But you also need to add new job applications, right? Thus, in this step, you build the route for adding new job applications. First of all, write the following code in and then I will explain what it does: controllers.js addApplication = (req, res) => { newPage = notion.pages.create({ : { : process.env.DB_ID }, : { : { : [ { : { : req.body.company } } ] }, : { : { : status } }, : { : { : req.body.status } }, : { : { : req.body.deadline } }, : { : req.body.url }, : { : [ { : { : req.body.comments } } ] } } }); newPage; }; const async const await parent database_id properties Company title text content Position select name Status select name 'Next Deadline' date start 'Job Description' url Comments rich_text text content return In the above code snippet, you call the method using the Notion client, and you pass two things: create - you pass the database parent. You do so by using the database ID from the .env file. database parent - these properties represent the Notion document fields. properties After that, for each property, you add the properties passed in the body of the request. Making a request with the following data: POST { : , : , : , : , : , : } "company" "CatalinsTech" "position" "Technical Writer" "status" "Applied" "deadline" "2021-12-12" "url" "https://catalins.tech" "comments" "I hope to get the job" Adds a new job application to the Notion document. You can see that the job application was added by checking . this link Figure 10 illustrates the request made to the URL s. POST http://localhost:3333/job/application Update existing application Updating an existing job application is very similar to adding a new one. There are two main differences: you use the method update you pass the rather than the page ID database ID You pass the page ID in the URL. For instance, you will make a request to the URL where you replace with the actual ID of the document. You can access the ID from the URL in the request parameters - . PATCH /job/applications/:id :id req.params.id Write the following code in the file: controllers.js updateApplication = (req, res) => { updatedPage = notion.pages.update({ : req.params.id, : { : { : [ { : { : req.body.company } } ] }, : { : { : req.body.position } }, : { : { : req.body.status } }, : { : { : req.body.deadline } }, : { : req.body.url }, : { : [ { : { : req.body.comments } } ] } } }); updatedPage; }; const async const await page_id properties Company title text content Position select name Status select name 'Next Deadline' date start 'Job Description' url Comments rich_text text content return Let's modify the last job application added - to the company . Update the following fields: CatalinsTech status - update it to Signed deadline - update it to 2021-05-27 comments - update it to I got the job Thus, make a request to with the following JSON data: PATCH http://localhost:3333/job/applications/32f1f59b-9176-4b4b-ab52-1ce66a77df25 { : , : , : , : , : , : } "company" "CatalinsTech" "position" "Technical Writer" "status" "Signed" "deadline" "2021-05-27" "url" "https://catalins.tech" "comments" "I got the job" Figure 11 illustrates the request being successful. The job application was updated, and you can check it . here Thus, this is how you can update your existing job applications programmatically. Export the controllers Before you can use the controllers, you need to export them. The reason why you need to export them is that you use them in the file. routes.js Thus, add the end of the file called , add the following code: controllers.js .exports = { getAllApplications, getApplication, addApplication, updateApplication, filterApplications }; module Now you can run the application and play with it! Check to see the complete controllers.js file. this gist How to run the application You can start the application by running in the root folder. node app.js Once you run that, the application should start and you can make HTTP requests. However, if you clone the application from GitHub, you need to install the dependencies first. You can do so by running: npm install node app.js Conclusion This the end of the tutorial! You build a Node.js API that allows you to: Create and update job applications Retrieve all job applications Access only a specific job application Filter the job applications by the company name Unfortunately, the Notion API does not allow us to delete resources. That is why there is no endpoint. DELETE Check the full application code on the . GitHub repository Previously published at https://catalins.tech/track-job-applications-with-notion-api-nodejs-and-fastifyjs