[{"data":1,"prerenderedAt":4098},["ShallowReactive",2],{"post-react-app-with-mongodb-atlas-app-services":3},{"id":4,"title":5,"body":6,"canonicalUrl":4082,"cover":4083,"date":4084,"description":13,"draft":4085,"extension":4086,"hashnodeId":4087,"meta":4088,"navigation":135,"path":4089,"seo":4090,"slug":4091,"stem":4091,"tags":4092,"__hash__":4097},"posts\u002Freact-app-with-mongodb-atlas-app-services.md","Create a personal expense tracker using MongoDB Atlas App Services & Triggers",{"type":7,"value":8,"toc":4058},"minimark",[9,14,18,22,25,28,41,44,48,51,65,68,73,80,177,180,269,273,289,459,463,486,565,569,576,1086,1089,1096,1100,1103,1762,1765,1771,1775,1786,1941,1948,1954,1958,1971,1975,1982,1988,1994,2001,2007,2011,2026,2032,2039,2045,2049,2052,2058,2067,2073,2077,2083,2093,2099,2126,2132,2142,2242,2249,2253,2265,2268,2318,2329,2371,2377,2615,2621,2711,2718,2740,2743,2748,2754,2759,2765,2779,2785,2792,2798,2801,2805,2811,2915,2918,2927,2934,2943,2961,2967,2982,3021,3027,3052,3055,3059,3066,3094,3116,3123,3306,3309,3313,3316,3362,3365,3371,3374,3380,3390,3529,3534,3668,3671,3677,3680,3683,3754,3765,3771,3775,3786,3819,3825,3832,4014,4017,4021,4024,4040,4043,4051,4054],[10,11,13],"h2",{"id":12},"introduction","Introduction",[15,16,17],"p",{},"Did you know that Atlas App Services is a fully managed cloud service offering from MongoDB that we can use as a backend for our apps? This article provides a step-by-step guide on how to use MongoDB Atlas App Services and its various triggers for creating an app. The app is a basic personal expense tracker, and we will be using the React library to code it from scratch.",[10,19,21],{"id":20},"what-is-a-trigger","What is a trigger?",[15,23,24],{},"Before moving ahead, let's answer this question first. What is a trigger? I'm sure all of us are familiar with the most common usage of this word, but it is much more than that. \"A trigger is a cause or an event that precedes or starts an action\". It is like \"if-this-then-that\" but at a different layer and with a broader scope. Triggers play a crucial role in the smooth functioning of any serverless application development, we must have a good grasp of them.",[15,26,27],{},"So which triggers and actions we're talking about here considering application development in general and our app in particular? Well, some of the examples can be",[29,30,31,35,38],"ol",{},[32,33,34],"li",{},"If someone signs up for our app, then we can send them a welcome email, and\u002For create a user entry in our database.",[32,36,37],{},"If a user does something inside the app, say creates a new transaction then we can update their balance for quick retrieval",[32,39,40],{},"If there is an action that happens on a regular schedule, then maybe we can automate it instead of updating it manually, and so on...",[15,42,43],{},"There can be many more examples, but these 3 are sufficient to understand the triggers which App services offer. The first one is called the \"Auth Trigger\" as we're doing something because of the auth event. The second one is a database trigger, as when a new entry is added to the database we do something else asynchronously. And the last one is a scheduled trigger because it happens on a regular schedule. Now let's dive into the app we're going to build.",[10,45,47],{"id":46},"the-frontend","The Frontend",[15,49,50],{},"As we're building a personal expense tracker, at the bare minimum it needs to have the following features",[29,52,53,56,59,62],{},[32,54,55],{},"Ability to create an account so that we can associate the transactions with a particular user. To keep it simple we'll be using anonymous login to achieve it",[32,57,58],{},"Ability to add manual entries for any credit or debit",[32,60,61],{},"A basic dashboard where we can get a holistic view of our finances for the month, and also see the individual transactions",[32,63,64],{},"On change of month reset the credits\u002Fdebits and possibly do other related chores",[15,66,67],{},"Now that the scope of the work is defined, let's start building it",[69,70,72],"h3",{"id":71},"setting-up-the-react-app","Setting up the React App",[15,74,75,76],{},"Let's quickly set up a React project using the following set of commands in your terminal window. ",[77,78,79],"em",{},"I'm using \"yarn\" as my package manager, you can use commands specific to your preferred package manager.",[81,82,87],"pre",{"className":83,"code":84,"language":85,"meta":86,"style":86},"language-bash shiki shiki-themes github-light github-dark","# Create the project dir & client subdir and immediately cd into it.\n# \"mkdir -p\" creates the non existant parent dir.\nmkdir -p my-expenses-tracker\u002Fclient && cd $_\n\n# Create a React app in the current dir (client)\nyarn create react-app .\n\n# Run the app and start the dev server\nyarn start\n","bash","",[88,89,90,99,105,130,137,143,158,163,169],"code",{"__ignoreMap":86},[91,92,95],"span",{"class":93,"line":94},"line",1,[91,96,98],{"class":97},"sJ8bj","# Create the project dir & client subdir and immediately cd into it.\n",[91,100,102],{"class":93,"line":101},2,[91,103,104],{"class":97},"# \"mkdir -p\" creates the non existant parent dir.\n",[91,106,108,112,116,120,124,127],{"class":93,"line":107},3,[91,109,111],{"class":110},"sScJk","mkdir",[91,113,115],{"class":114},"sj4cs"," -p",[91,117,119],{"class":118},"sZZnC"," my-expenses-tracker\u002Fclient",[91,121,123],{"class":122},"sVt8B"," && ",[91,125,126],{"class":114},"cd",[91,128,129],{"class":114}," $_\n",[91,131,133],{"class":93,"line":132},4,[91,134,136],{"emptyLinePlaceholder":135},true,"\n",[91,138,140],{"class":93,"line":139},5,[91,141,142],{"class":97},"# Create a React app in the current dir (client)\n",[91,144,146,149,152,155],{"class":93,"line":145},6,[91,147,148],{"class":110},"yarn",[91,150,151],{"class":118}," create",[91,153,154],{"class":118}," react-app",[91,156,157],{"class":118}," .\n",[91,159,161],{"class":93,"line":160},7,[91,162,136],{"emptyLinePlaceholder":135},[91,164,166],{"class":93,"line":165},8,[91,167,168],{"class":97},"# Run the app and start the dev server\n",[91,170,172,174],{"class":93,"line":171},9,[91,173,148],{"class":110},[91,175,176],{"class":118}," start\n",[15,178,179],{},"Open another terminal window, navigate to the client folder and run the following commands.",[81,181,183],{"className":83,"code":182,"language":85,"meta":86,"style":86},"# Add react-router-dom and react-icons to the project\nyarn add react-router-dom react-icons\n\n# Create routes & components folders inside src dir\nmkdir src\u002Froutes src\u002Fcomponents\n\n# Create the initial pages & components\ntouch src\u002Froutes\u002FDashboard.js src\u002Froutes\u002FAddExpense.js src\u002Fcomponents\u002FNavbar.js\n\n# Open the project in VS Code\ncd .. && code .\n",[88,184,185,190,203,207,212,222,226,231,245,249,255],{"__ignoreMap":86},[91,186,187],{"class":93,"line":94},[91,188,189],{"class":97},"# Add react-router-dom and react-icons to the project\n",[91,191,192,194,197,200],{"class":93,"line":101},[91,193,148],{"class":110},[91,195,196],{"class":118}," add",[91,198,199],{"class":118}," react-router-dom",[91,201,202],{"class":118}," react-icons\n",[91,204,205],{"class":93,"line":107},[91,206,136],{"emptyLinePlaceholder":135},[91,208,209],{"class":93,"line":132},[91,210,211],{"class":97},"# Create routes & components folders inside src dir\n",[91,213,214,216,219],{"class":93,"line":139},[91,215,111],{"class":110},[91,217,218],{"class":118}," src\u002Froutes",[91,220,221],{"class":118}," src\u002Fcomponents\n",[91,223,224],{"class":93,"line":145},[91,225,136],{"emptyLinePlaceholder":135},[91,227,228],{"class":93,"line":160},[91,229,230],{"class":97},"# Create the initial pages & components\n",[91,232,233,236,239,242],{"class":93,"line":165},[91,234,235],{"class":110},"touch",[91,237,238],{"class":118}," src\u002Froutes\u002FDashboard.js",[91,240,241],{"class":118}," src\u002Froutes\u002FAddExpense.js",[91,243,244],{"class":118}," src\u002Fcomponents\u002FNavbar.js\n",[91,246,247],{"class":93,"line":171},[91,248,136],{"emptyLinePlaceholder":135},[91,250,252],{"class":93,"line":251},10,[91,253,254],{"class":97},"# Open the project in VS Code\n",[91,256,258,260,263,265,267],{"class":93,"line":257},11,[91,259,126],{"class":114},[91,261,262],{"class":118}," ..",[91,264,123],{"class":122},[91,266,88],{"class":110},[91,268,157],{"class":118},[69,270,272],{"id":271},"creating-the-routes","Creating the routes",[15,274,275,276,280,281,284,285,288],{},"We'll be adding two routes to the app: ",[277,278,279],"strong",{},"1."," the dashboard page, and ",[277,282,283],{},"2."," the new transaction page. Replace the content of the ",[88,286,287],{},"App.js"," file with the following:",[81,290,294],{"className":291,"code":292,"language":293,"meta":86,"style":86},"language-javascript shiki shiki-themes github-light github-dark","import { BrowserRouter, Routes, Route, Outlet } from 'react-router-dom';\n\nimport { Dashboard } from '.\u002Froutes\u002FDashboard';\nimport { NewTransaction } from '.\u002Froutes\u002FNewTransaction';\nimport { Navbar } from '.\u002Fcomponents\u002FNavbar';\nimport '.\u002FApp.css';\n\nconst Layout = () => {\n  return (\n    \u003Cdiv className='app'>\n      \u003CNavbar \u002F>\n      \u003COutlet \u002F>\n    \u003C\u002Fdiv>\n  );\n};\n\nfunction App() {\n  return (\n    \u003CBrowserRouter>\n      \u003CRoutes>\n        \u003CRoute path='\u002F' element={\u003CLayout \u002F>}>\n          \u003CRoute index element={\u003CDashboard \u002F>} \u002F>\n          \u003CRoute path='\u002Fnew' element={\u003CNewTransaction \u002F>} \u002F>\n        \u003C\u002FRoute>\n      \u003C\u002FRoutes>\n    \u003C\u002FBrowserRouter>\n  );\n}\n\nexport default App;\n","javascript",[88,295,296,301,305,310,315,320,325,329,334,339,344,349,355,361,367,373,378,384,389,395,401,407,413,419,425,431,437,442,448,453],{"__ignoreMap":86},[91,297,298],{"class":93,"line":94},[91,299,300],{},"import { BrowserRouter, Routes, Route, Outlet } from 'react-router-dom';\n",[91,302,303],{"class":93,"line":101},[91,304,136],{"emptyLinePlaceholder":135},[91,306,307],{"class":93,"line":107},[91,308,309],{},"import { Dashboard } from '.\u002Froutes\u002FDashboard';\n",[91,311,312],{"class":93,"line":132},[91,313,314],{},"import { NewTransaction } from '.\u002Froutes\u002FNewTransaction';\n",[91,316,317],{"class":93,"line":139},[91,318,319],{},"import { Navbar } from '.\u002Fcomponents\u002FNavbar';\n",[91,321,322],{"class":93,"line":145},[91,323,324],{},"import '.\u002FApp.css';\n",[91,326,327],{"class":93,"line":160},[91,328,136],{"emptyLinePlaceholder":135},[91,330,331],{"class":93,"line":165},[91,332,333],{},"const Layout = () => {\n",[91,335,336],{"class":93,"line":171},[91,337,338],{},"  return (\n",[91,340,341],{"class":93,"line":251},[91,342,343],{},"    \u003Cdiv className='app'>\n",[91,345,346],{"class":93,"line":257},[91,347,348],{},"      \u003CNavbar \u002F>\n",[91,350,352],{"class":93,"line":351},12,[91,353,354],{},"      \u003COutlet \u002F>\n",[91,356,358],{"class":93,"line":357},13,[91,359,360],{},"    \u003C\u002Fdiv>\n",[91,362,364],{"class":93,"line":363},14,[91,365,366],{},"  );\n",[91,368,370],{"class":93,"line":369},15,[91,371,372],{},"};\n",[91,374,376],{"class":93,"line":375},16,[91,377,136],{"emptyLinePlaceholder":135},[91,379,381],{"class":93,"line":380},17,[91,382,383],{},"function App() {\n",[91,385,387],{"class":93,"line":386},18,[91,388,338],{},[91,390,392],{"class":93,"line":391},19,[91,393,394],{},"    \u003CBrowserRouter>\n",[91,396,398],{"class":93,"line":397},20,[91,399,400],{},"      \u003CRoutes>\n",[91,402,404],{"class":93,"line":403},21,[91,405,406],{},"        \u003CRoute path='\u002F' element={\u003CLayout \u002F>}>\n",[91,408,410],{"class":93,"line":409},22,[91,411,412],{},"          \u003CRoute index element={\u003CDashboard \u002F>} \u002F>\n",[91,414,416],{"class":93,"line":415},23,[91,417,418],{},"          \u003CRoute path='\u002Fnew' element={\u003CNewTransaction \u002F>} \u002F>\n",[91,420,422],{"class":93,"line":421},24,[91,423,424],{},"        \u003C\u002FRoute>\n",[91,426,428],{"class":93,"line":427},25,[91,429,430],{},"      \u003C\u002FRoutes>\n",[91,432,434],{"class":93,"line":433},26,[91,435,436],{},"    \u003C\u002FBrowserRouter>\n",[91,438,440],{"class":93,"line":439},27,[91,441,366],{},[91,443,445],{"class":93,"line":444},28,[91,446,447],{},"}\n",[91,449,451],{"class":93,"line":450},29,[91,452,136],{"emptyLinePlaceholder":135},[91,454,456],{"class":93,"line":455},30,[91,457,458],{},"export default App;\n",[69,460,462],{"id":461},"the-navbar-component","The Navbar component",[15,464,465,466,469,470,473,474,477,478,485],{},"Add the following code into the ",[88,467,468],{},"Navbar.js"," file that we created earlier. You can get the image assets, as well as the CSS files (",[88,471,472],{},"index.css"," & ",[88,475,476],{},"App.css",") from ",[479,480,484],"a",{"href":481,"rel":482},"https:\u002F\u002Fgithub.com\u002Fra-jeev\u002Fexpense-buddy",[483],"nofollow","the Github repo",".",[81,487,489],{"className":291,"code":488,"language":293,"meta":86,"style":86},"import { NavLink } from 'react-router-dom';\nimport { FaPlusCircle } from 'react-icons\u002Ffa';\n\nexport const Navbar = () => {\n  return (\n    \u003Cnav className='navbar '>\n      \u003CNavLink className='logo nav-link' to='\u002F'>\n        Expense Buddy\n      \u003C\u002FNavLink>\n\n      \u003CNavLink className='nav-link' to='\u002Fnew'>\n        \u003CFaPlusCircle \u002F> Add New\n      \u003C\u002FNavLink>\n    \u003C\u002Fnav>\n  );\n};\n",[88,490,491,496,501,505,510,514,519,524,529,534,538,543,548,552,557,561],{"__ignoreMap":86},[91,492,493],{"class":93,"line":94},[91,494,495],{},"import { NavLink } from 'react-router-dom';\n",[91,497,498],{"class":93,"line":101},[91,499,500],{},"import { FaPlusCircle } from 'react-icons\u002Ffa';\n",[91,502,503],{"class":93,"line":107},[91,504,136],{"emptyLinePlaceholder":135},[91,506,507],{"class":93,"line":132},[91,508,509],{},"export const Navbar = () => {\n",[91,511,512],{"class":93,"line":139},[91,513,338],{},[91,515,516],{"class":93,"line":145},[91,517,518],{},"    \u003Cnav className='navbar '>\n",[91,520,521],{"class":93,"line":160},[91,522,523],{},"      \u003CNavLink className='logo nav-link' to='\u002F'>\n",[91,525,526],{"class":93,"line":165},[91,527,528],{},"        Expense Buddy\n",[91,530,531],{"class":93,"line":171},[91,532,533],{},"      \u003C\u002FNavLink>\n",[91,535,536],{"class":93,"line":251},[91,537,136],{"emptyLinePlaceholder":135},[91,539,540],{"class":93,"line":257},[91,541,542],{},"      \u003CNavLink className='nav-link' to='\u002Fnew'>\n",[91,544,545],{"class":93,"line":351},[91,546,547],{},"        \u003CFaPlusCircle \u002F> Add New\n",[91,549,550],{"class":93,"line":357},[91,551,533],{},[91,553,554],{"class":93,"line":363},[91,555,556],{},"    \u003C\u002Fnav>\n",[91,558,559],{"class":93,"line":369},[91,560,366],{},[91,562,563],{"class":93,"line":375},[91,564,372],{},[69,566,568],{"id":567},"dashboard-page","Dashboard Page",[15,570,571,572,575],{},"Add the following code to the ",[88,573,574],{},"Dashboard.js"," file. We'll come back to this file and modify it to add the backend interaction later on.",[81,577,579],{"className":291,"code":578,"language":293,"meta":86,"style":86},"import { useState, useEffect } from 'react';\nimport { useNavigate } from 'react-router-dom';\nimport { FaPlusCircle } from 'react-icons\u002Ffa';\n\nimport { formatDateTime, formatCurrency } from '..\u002Futils';\nimport AddImage from '..\u002Fassets\u002Fimages\u002Fadd-notes.svg';\n\nexport const Dashboard = () => {\n  const [user, setUser] = useState();\n  const [transactions, setTransactions] = useState([]);\n  const [loading, setLoading] = useState(false);\n\n  const navigate = useNavigate();\n\n  return (\n    \u003Cdiv className='container'>\n      {loading ? (\n        \u003Cdiv className='loader'>Loading...\u003C\u002Fdiv>\n      ) : transactions.length ? (\n        \u003Cdiv className='dashboard'>\n          {user && (\n            \u003Cdiv className='card summary-card'>\n              \u003Ch2>This month\u003C\u002Fh2>\n\n              \u003Cdiv className='details'>\n                \u003Cdiv>Current Balance\u003C\u002Fdiv>\n                \u003Cdiv className='details-value'>\n                  {formatCurrency(user.balance)}\n                \u003C\u002Fdiv>\n              \u003C\u002Fdiv>\n\n              \u003Cdiv className='card-row'>\n                \u003Cdiv className='details money-in'>\n                  \u003Cdiv className='details-label'>Total money in\u003C\u002Fdiv>\n                  \u003Cdiv className='details-value'>\n                    {formatCurrency(user.currMonth.in)}\n                  \u003C\u002Fdiv>\n                \u003C\u002Fdiv>\n                \u003Cdiv className='details money-out'>\n                  \u003Cdiv className='details-label'>Total money out\u003C\u002Fdiv>\n                  \u003Cdiv className='details-value'>\n                    {formatCurrency(user.currMonth.out)}\n                  \u003C\u002Fdiv>\n                \u003C\u002Fdiv>\n              \u003C\u002Fdiv>\n            \u003C\u002Fdiv>\n          )}\n\n          \u003Ch3 className='transactions-title'>Transactions\u003C\u002Fh3>\n\n          {transactions.map((transaction) => {\n            return (\n              \u003Cdiv\n                key={transaction._id}\n                className={`card transaction-card ${\n                  transaction.type === 'IN'\n                    ? 'transaction-in'\n                    : 'transaction-out'\n                }`}\n              >\n                \u003Cdiv>\n                  \u003Cdiv>{transaction.comment}\u003C\u002Fdiv>\n                  \u003Cdiv className='transaction-date'>\n                    {formatDateTime(transaction.createdAt)}\n                  \u003C\u002Fdiv>\n                \u003C\u002Fdiv>\n                \u003Cdiv className='transaction-value'>\n                  {formatCurrency(transaction.amount)}\n                \u003C\u002Fdiv>\n              \u003C\u002Fdiv>\n            );\n          })}\n        \u003C\u002Fdiv>\n      ) : (\n        \u003Cdiv className='no-data'>\n          \u003Cimg\n            className='no-data-img'\n            src={AddImage}\n            alt='No transactions found, add one'\n          \u002F>\n          \u003Cdiv className='no-data-text'>No transactions found\u003C\u002Fdiv>\n          \u003Cbutton\n            type='button'\n            className='btn btn-primary'\n            onClick={() => navigate('\u002Fnew')}\n          >\n            \u003CFaPlusCircle \u002F> Add Transaction\n          \u003C\u002Fbutton>\n        \u003C\u002Fdiv>\n      )}\n    \u003C\u002Fdiv>\n  );\n};\n",[88,580,581,586,591,595,599,604,609,613,618,623,628,633,637,642,646,650,655,660,665,670,675,680,685,690,694,699,704,709,714,719,724,729,735,741,747,753,759,765,770,776,782,787,793,798,803,808,814,820,825,831,836,842,848,854,860,866,872,878,884,890,896,902,908,914,920,925,930,936,942,947,952,958,964,970,976,982,988,994,1000,1006,1012,1018,1024,1030,1036,1042,1048,1054,1060,1065,1071,1076,1081],{"__ignoreMap":86},[91,582,583],{"class":93,"line":94},[91,584,585],{},"import { useState, useEffect } from 'react';\n",[91,587,588],{"class":93,"line":101},[91,589,590],{},"import { useNavigate } from 'react-router-dom';\n",[91,592,593],{"class":93,"line":107},[91,594,500],{},[91,596,597],{"class":93,"line":132},[91,598,136],{"emptyLinePlaceholder":135},[91,600,601],{"class":93,"line":139},[91,602,603],{},"import { formatDateTime, formatCurrency } from '..\u002Futils';\n",[91,605,606],{"class":93,"line":145},[91,607,608],{},"import AddImage from '..\u002Fassets\u002Fimages\u002Fadd-notes.svg';\n",[91,610,611],{"class":93,"line":160},[91,612,136],{"emptyLinePlaceholder":135},[91,614,615],{"class":93,"line":165},[91,616,617],{},"export const Dashboard = () => {\n",[91,619,620],{"class":93,"line":171},[91,621,622],{},"  const [user, setUser] = useState();\n",[91,624,625],{"class":93,"line":251},[91,626,627],{},"  const [transactions, setTransactions] = useState([]);\n",[91,629,630],{"class":93,"line":257},[91,631,632],{},"  const [loading, setLoading] = useState(false);\n",[91,634,635],{"class":93,"line":351},[91,636,136],{"emptyLinePlaceholder":135},[91,638,639],{"class":93,"line":357},[91,640,641],{},"  const navigate = useNavigate();\n",[91,643,644],{"class":93,"line":363},[91,645,136],{"emptyLinePlaceholder":135},[91,647,648],{"class":93,"line":369},[91,649,338],{},[91,651,652],{"class":93,"line":375},[91,653,654],{},"    \u003Cdiv className='container'>\n",[91,656,657],{"class":93,"line":380},[91,658,659],{},"      {loading ? (\n",[91,661,662],{"class":93,"line":386},[91,663,664],{},"        \u003Cdiv className='loader'>Loading...\u003C\u002Fdiv>\n",[91,666,667],{"class":93,"line":391},[91,668,669],{},"      ) : transactions.length ? (\n",[91,671,672],{"class":93,"line":397},[91,673,674],{},"        \u003Cdiv className='dashboard'>\n",[91,676,677],{"class":93,"line":403},[91,678,679],{},"          {user && (\n",[91,681,682],{"class":93,"line":409},[91,683,684],{},"            \u003Cdiv className='card summary-card'>\n",[91,686,687],{"class":93,"line":415},[91,688,689],{},"              \u003Ch2>This month\u003C\u002Fh2>\n",[91,691,692],{"class":93,"line":421},[91,693,136],{"emptyLinePlaceholder":135},[91,695,696],{"class":93,"line":427},[91,697,698],{},"              \u003Cdiv className='details'>\n",[91,700,701],{"class":93,"line":433},[91,702,703],{},"                \u003Cdiv>Current Balance\u003C\u002Fdiv>\n",[91,705,706],{"class":93,"line":439},[91,707,708],{},"                \u003Cdiv className='details-value'>\n",[91,710,711],{"class":93,"line":444},[91,712,713],{},"                  {formatCurrency(user.balance)}\n",[91,715,716],{"class":93,"line":450},[91,717,718],{},"                \u003C\u002Fdiv>\n",[91,720,721],{"class":93,"line":455},[91,722,723],{},"              \u003C\u002Fdiv>\n",[91,725,727],{"class":93,"line":726},31,[91,728,136],{"emptyLinePlaceholder":135},[91,730,732],{"class":93,"line":731},32,[91,733,734],{},"              \u003Cdiv className='card-row'>\n",[91,736,738],{"class":93,"line":737},33,[91,739,740],{},"                \u003Cdiv className='details money-in'>\n",[91,742,744],{"class":93,"line":743},34,[91,745,746],{},"                  \u003Cdiv className='details-label'>Total money in\u003C\u002Fdiv>\n",[91,748,750],{"class":93,"line":749},35,[91,751,752],{},"                  \u003Cdiv className='details-value'>\n",[91,754,756],{"class":93,"line":755},36,[91,757,758],{},"                    {formatCurrency(user.currMonth.in)}\n",[91,760,762],{"class":93,"line":761},37,[91,763,764],{},"                  \u003C\u002Fdiv>\n",[91,766,768],{"class":93,"line":767},38,[91,769,718],{},[91,771,773],{"class":93,"line":772},39,[91,774,775],{},"                \u003Cdiv className='details money-out'>\n",[91,777,779],{"class":93,"line":778},40,[91,780,781],{},"                  \u003Cdiv className='details-label'>Total money out\u003C\u002Fdiv>\n",[91,783,785],{"class":93,"line":784},41,[91,786,752],{},[91,788,790],{"class":93,"line":789},42,[91,791,792],{},"                    {formatCurrency(user.currMonth.out)}\n",[91,794,796],{"class":93,"line":795},43,[91,797,764],{},[91,799,801],{"class":93,"line":800},44,[91,802,718],{},[91,804,806],{"class":93,"line":805},45,[91,807,723],{},[91,809,811],{"class":93,"line":810},46,[91,812,813],{},"            \u003C\u002Fdiv>\n",[91,815,817],{"class":93,"line":816},47,[91,818,819],{},"          )}\n",[91,821,823],{"class":93,"line":822},48,[91,824,136],{"emptyLinePlaceholder":135},[91,826,828],{"class":93,"line":827},49,[91,829,830],{},"          \u003Ch3 className='transactions-title'>Transactions\u003C\u002Fh3>\n",[91,832,834],{"class":93,"line":833},50,[91,835,136],{"emptyLinePlaceholder":135},[91,837,839],{"class":93,"line":838},51,[91,840,841],{},"          {transactions.map((transaction) => {\n",[91,843,845],{"class":93,"line":844},52,[91,846,847],{},"            return (\n",[91,849,851],{"class":93,"line":850},53,[91,852,853],{},"              \u003Cdiv\n",[91,855,857],{"class":93,"line":856},54,[91,858,859],{},"                key={transaction._id}\n",[91,861,863],{"class":93,"line":862},55,[91,864,865],{},"                className={`card transaction-card ${\n",[91,867,869],{"class":93,"line":868},56,[91,870,871],{},"                  transaction.type === 'IN'\n",[91,873,875],{"class":93,"line":874},57,[91,876,877],{},"                    ? 'transaction-in'\n",[91,879,881],{"class":93,"line":880},58,[91,882,883],{},"                    : 'transaction-out'\n",[91,885,887],{"class":93,"line":886},59,[91,888,889],{},"                }`}\n",[91,891,893],{"class":93,"line":892},60,[91,894,895],{},"              >\n",[91,897,899],{"class":93,"line":898},61,[91,900,901],{},"                \u003Cdiv>\n",[91,903,905],{"class":93,"line":904},62,[91,906,907],{},"                  \u003Cdiv>{transaction.comment}\u003C\u002Fdiv>\n",[91,909,911],{"class":93,"line":910},63,[91,912,913],{},"                  \u003Cdiv className='transaction-date'>\n",[91,915,917],{"class":93,"line":916},64,[91,918,919],{},"                    {formatDateTime(transaction.createdAt)}\n",[91,921,923],{"class":93,"line":922},65,[91,924,764],{},[91,926,928],{"class":93,"line":927},66,[91,929,718],{},[91,931,933],{"class":93,"line":932},67,[91,934,935],{},"                \u003Cdiv className='transaction-value'>\n",[91,937,939],{"class":93,"line":938},68,[91,940,941],{},"                  {formatCurrency(transaction.amount)}\n",[91,943,945],{"class":93,"line":944},69,[91,946,718],{},[91,948,950],{"class":93,"line":949},70,[91,951,723],{},[91,953,955],{"class":93,"line":954},71,[91,956,957],{},"            );\n",[91,959,961],{"class":93,"line":960},72,[91,962,963],{},"          })}\n",[91,965,967],{"class":93,"line":966},73,[91,968,969],{},"        \u003C\u002Fdiv>\n",[91,971,973],{"class":93,"line":972},74,[91,974,975],{},"      ) : (\n",[91,977,979],{"class":93,"line":978},75,[91,980,981],{},"        \u003Cdiv className='no-data'>\n",[91,983,985],{"class":93,"line":984},76,[91,986,987],{},"          \u003Cimg\n",[91,989,991],{"class":93,"line":990},77,[91,992,993],{},"            className='no-data-img'\n",[91,995,997],{"class":93,"line":996},78,[91,998,999],{},"            src={AddImage}\n",[91,1001,1003],{"class":93,"line":1002},79,[91,1004,1005],{},"            alt='No transactions found, add one'\n",[91,1007,1009],{"class":93,"line":1008},80,[91,1010,1011],{},"          \u002F>\n",[91,1013,1015],{"class":93,"line":1014},81,[91,1016,1017],{},"          \u003Cdiv className='no-data-text'>No transactions found\u003C\u002Fdiv>\n",[91,1019,1021],{"class":93,"line":1020},82,[91,1022,1023],{},"          \u003Cbutton\n",[91,1025,1027],{"class":93,"line":1026},83,[91,1028,1029],{},"            type='button'\n",[91,1031,1033],{"class":93,"line":1032},84,[91,1034,1035],{},"            className='btn btn-primary'\n",[91,1037,1039],{"class":93,"line":1038},85,[91,1040,1041],{},"            onClick={() => navigate('\u002Fnew')}\n",[91,1043,1045],{"class":93,"line":1044},86,[91,1046,1047],{},"          >\n",[91,1049,1051],{"class":93,"line":1050},87,[91,1052,1053],{},"            \u003CFaPlusCircle \u002F> Add Transaction\n",[91,1055,1057],{"class":93,"line":1056},88,[91,1058,1059],{},"          \u003C\u002Fbutton>\n",[91,1061,1063],{"class":93,"line":1062},89,[91,1064,969],{},[91,1066,1068],{"class":93,"line":1067},90,[91,1069,1070],{},"      )}\n",[91,1072,1074],{"class":93,"line":1073},91,[91,1075,360],{},[91,1077,1079],{"class":93,"line":1078},92,[91,1080,366],{},[91,1082,1084],{"class":93,"line":1083},93,[91,1085,372],{},[15,1087,1088],{},"Without anything to show, the page looks like the below screenshot",[15,1090,1091],{},[1092,1093],"img",{"alt":1094,"src":1095},"dashboard page without any data","\u002Fimages\u002Fposts\u002Freact-app-with-mongodb-atlas-app-services\u002F2a4e7af6-2aa0-4a7d-bd2d-b2a0ec7318b0-aace308e8d.png",[69,1097,1099],{"id":1098},"newtransaction-page","NewTransaction Page",[15,1101,1102],{},"Here we create a form to submit a new transaction to the database. Right now it is just the barebones file, we'll add the backend interaction later on",[81,1104,1106],{"className":291,"code":1105,"language":293,"meta":86,"style":86},"import { useState } from 'react';\nimport { useNavigate } from 'react-router-dom';\n\nconst INITIAL_STATE = {\n  comment: '',\n  amount: '',\n  type: '',\n};\n\nconst TRANSACTION_TYPES = {\n  SELECT: 'Select a type',\n  IN: 'Add',\n  OUT: 'Deduct',\n};\n\nexport const NewTransaction = () => {\n  const [formState, setFormState] = useState(INITIAL_STATE);\n  const [loading, setLoading] = useState(false);\n  const [message, setMessage] = useState(null);\n\n  const navigate = useNavigate();\n\n  const setInput = (key, value) => {\n    setFormState({ ...formState, [key]: value });\n  };\n\n  useEffect(() => {\n    if (message) {\n      const timerId = setTimeout(() => {\n        if (message.type === 'success') {\n          navigate('\u002F', { replace: true });\n        }\n        setMessage(null);\n      }, 2000);\n\n      return () => clearTimeout(timerId);\n    }\n  }, [message, navigate]);\n\n  const onSubmit = async (e) => {\n    e.preventDefault();\n\n    const amount = parseFloat(formState.amount);\n    const comment = formState.comment.trim();\n\n    if (!amount || !comment || !formState.type) {\n      alert('Please fill in all fields');\n      return;\n    }\n\n    try {\n      console.log('final transaction data', {\n        ...formState,\n        createdAt: new Date(),\n      });\n    } catch (error) {\n      console.log('failed to save the transaction');\n    }\n  };\n\n  return (\n    \u003Cdiv className='container'>\n      \u003Cdiv className='card transaction-form'>\n        \u003Ch2>Add transaction details\u003C\u002Fh2>\n        \u003Cform onSubmit={onSubmit}>\n          \u003Cdiv className='form-group'>\n            \u003Clabel htmlFor='name'>Transaction amount\u003C\u002Flabel>\n            \u003Cinput\n              type='number'\n              name='amount'\n              id='amount'\n              placeholder='Enter the amount'\n              value={formState.amount}\n              onChange={(e) => setInput('amount', e.target.value)}\n            \u002F>\n          \u003C\u002Fdiv>\n          \u003Cdiv className='form-group'>\n            \u003Clabel htmlFor='comment'>Transaction comment\u003C\u002Flabel>\n            \u003Cinput\n              type='text'\n              name='comment'\n              id='comment'\n              placeholder='Transaction comment'\n              value={formState.comment}\n              onChange={(e) => setInput('comment', e.target.value)}\n            \u002F>\n          \u003C\u002Fdiv>\n          \u003Cdiv className='form-group'>\n            \u003Clabel htmlFor='transaction-type'>Transaction type\u003C\u002Flabel>\n            \u003Cselect\n              name='transaction-type'\n              id='transaction-type'\n              value={formState.type}\n              onChange={(e) => setInput('type', e.target.value)}\n            >\n              {Object.keys(TRANSACTION_TYPES).map((type) => {\n                return (\n                  \u003Coption key={`type-${type}`} value={type}>\n                    {TRANSACTION_TYPES[type]}\n                  \u003C\u002Foption>\n                );\n              })}\n            \u003C\u002Fselect>\n          \u003C\u002Fdiv>\n\n          {message && (\n            \u003Cdiv className={`message-${message.type}`}>{message.text}\u003C\u002Fdiv>\n          )}\n\n          \u003Cdiv className='card-row'>\n            \u003Cbutton\n              className='btn btn-outlined'\n              disabled={loading}\n              type='button'\n              onClick={() => setFormState(INITIAL_STATE)}\n            >\n              Cancel\n            \u003C\u002Fbutton>\n            \u003Cbutton \n              className='btn btn-primary'\n              disabled={loading}\n              type='submit'\n            >\n              Save\n            \u003C\u002Fbutton>\n          \u003C\u002Fdiv>\n        \u003C\u002Fform>\n      \u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n  );\n};\n",[88,1107,1108,1113,1117,1121,1126,1131,1136,1141,1145,1149,1154,1159,1164,1169,1173,1177,1182,1187,1191,1196,1200,1204,1208,1213,1218,1223,1227,1232,1237,1242,1247,1252,1257,1262,1267,1271,1276,1281,1286,1290,1295,1300,1304,1309,1314,1318,1323,1328,1333,1337,1341,1346,1351,1356,1361,1366,1371,1376,1380,1384,1388,1392,1396,1401,1406,1411,1416,1421,1426,1431,1436,1441,1446,1451,1456,1461,1466,1470,1475,1479,1484,1489,1494,1499,1504,1509,1513,1517,1521,1526,1531,1536,1541,1546,1552,1558,1564,1570,1576,1582,1588,1594,1600,1606,1611,1616,1622,1628,1633,1638,1644,1650,1656,1662,1668,1674,1679,1685,1691,1697,1703,1708,1714,1719,1725,1730,1735,1741,1747,1752,1757],{"__ignoreMap":86},[91,1109,1110],{"class":93,"line":94},[91,1111,1112],{},"import { useState } from 'react';\n",[91,1114,1115],{"class":93,"line":101},[91,1116,590],{},[91,1118,1119],{"class":93,"line":107},[91,1120,136],{"emptyLinePlaceholder":135},[91,1122,1123],{"class":93,"line":132},[91,1124,1125],{},"const INITIAL_STATE = {\n",[91,1127,1128],{"class":93,"line":139},[91,1129,1130],{},"  comment: '',\n",[91,1132,1133],{"class":93,"line":145},[91,1134,1135],{},"  amount: '',\n",[91,1137,1138],{"class":93,"line":160},[91,1139,1140],{},"  type: '',\n",[91,1142,1143],{"class":93,"line":165},[91,1144,372],{},[91,1146,1147],{"class":93,"line":171},[91,1148,136],{"emptyLinePlaceholder":135},[91,1150,1151],{"class":93,"line":251},[91,1152,1153],{},"const TRANSACTION_TYPES = {\n",[91,1155,1156],{"class":93,"line":257},[91,1157,1158],{},"  SELECT: 'Select a type',\n",[91,1160,1161],{"class":93,"line":351},[91,1162,1163],{},"  IN: 'Add',\n",[91,1165,1166],{"class":93,"line":357},[91,1167,1168],{},"  OUT: 'Deduct',\n",[91,1170,1171],{"class":93,"line":363},[91,1172,372],{},[91,1174,1175],{"class":93,"line":369},[91,1176,136],{"emptyLinePlaceholder":135},[91,1178,1179],{"class":93,"line":375},[91,1180,1181],{},"export const NewTransaction = () => {\n",[91,1183,1184],{"class":93,"line":380},[91,1185,1186],{},"  const [formState, setFormState] = useState(INITIAL_STATE);\n",[91,1188,1189],{"class":93,"line":386},[91,1190,632],{},[91,1192,1193],{"class":93,"line":391},[91,1194,1195],{},"  const [message, setMessage] = useState(null);\n",[91,1197,1198],{"class":93,"line":397},[91,1199,136],{"emptyLinePlaceholder":135},[91,1201,1202],{"class":93,"line":403},[91,1203,641],{},[91,1205,1206],{"class":93,"line":409},[91,1207,136],{"emptyLinePlaceholder":135},[91,1209,1210],{"class":93,"line":415},[91,1211,1212],{},"  const setInput = (key, value) => {\n",[91,1214,1215],{"class":93,"line":421},[91,1216,1217],{},"    setFormState({ ...formState, [key]: value });\n",[91,1219,1220],{"class":93,"line":427},[91,1221,1222],{},"  };\n",[91,1224,1225],{"class":93,"line":433},[91,1226,136],{"emptyLinePlaceholder":135},[91,1228,1229],{"class":93,"line":439},[91,1230,1231],{},"  useEffect(() => {\n",[91,1233,1234],{"class":93,"line":444},[91,1235,1236],{},"    if (message) {\n",[91,1238,1239],{"class":93,"line":450},[91,1240,1241],{},"      const timerId = setTimeout(() => {\n",[91,1243,1244],{"class":93,"line":455},[91,1245,1246],{},"        if (message.type === 'success') {\n",[91,1248,1249],{"class":93,"line":726},[91,1250,1251],{},"          navigate('\u002F', { replace: true });\n",[91,1253,1254],{"class":93,"line":731},[91,1255,1256],{},"        }\n",[91,1258,1259],{"class":93,"line":737},[91,1260,1261],{},"        setMessage(null);\n",[91,1263,1264],{"class":93,"line":743},[91,1265,1266],{},"      }, 2000);\n",[91,1268,1269],{"class":93,"line":749},[91,1270,136],{"emptyLinePlaceholder":135},[91,1272,1273],{"class":93,"line":755},[91,1274,1275],{},"      return () => clearTimeout(timerId);\n",[91,1277,1278],{"class":93,"line":761},[91,1279,1280],{},"    }\n",[91,1282,1283],{"class":93,"line":767},[91,1284,1285],{},"  }, [message, navigate]);\n",[91,1287,1288],{"class":93,"line":772},[91,1289,136],{"emptyLinePlaceholder":135},[91,1291,1292],{"class":93,"line":778},[91,1293,1294],{},"  const onSubmit = async (e) => {\n",[91,1296,1297],{"class":93,"line":784},[91,1298,1299],{},"    e.preventDefault();\n",[91,1301,1302],{"class":93,"line":789},[91,1303,136],{"emptyLinePlaceholder":135},[91,1305,1306],{"class":93,"line":795},[91,1307,1308],{},"    const amount = parseFloat(formState.amount);\n",[91,1310,1311],{"class":93,"line":800},[91,1312,1313],{},"    const comment = formState.comment.trim();\n",[91,1315,1316],{"class":93,"line":805},[91,1317,136],{"emptyLinePlaceholder":135},[91,1319,1320],{"class":93,"line":810},[91,1321,1322],{},"    if (!amount || !comment || !formState.type) {\n",[91,1324,1325],{"class":93,"line":816},[91,1326,1327],{},"      alert('Please fill in all fields');\n",[91,1329,1330],{"class":93,"line":822},[91,1331,1332],{},"      return;\n",[91,1334,1335],{"class":93,"line":827},[91,1336,1280],{},[91,1338,1339],{"class":93,"line":833},[91,1340,136],{"emptyLinePlaceholder":135},[91,1342,1343],{"class":93,"line":838},[91,1344,1345],{},"    try {\n",[91,1347,1348],{"class":93,"line":844},[91,1349,1350],{},"      console.log('final transaction data', {\n",[91,1352,1353],{"class":93,"line":850},[91,1354,1355],{},"        ...formState,\n",[91,1357,1358],{"class":93,"line":856},[91,1359,1360],{},"        createdAt: new Date(),\n",[91,1362,1363],{"class":93,"line":862},[91,1364,1365],{},"      });\n",[91,1367,1368],{"class":93,"line":868},[91,1369,1370],{},"    } catch (error) {\n",[91,1372,1373],{"class":93,"line":874},[91,1374,1375],{},"      console.log('failed to save the transaction');\n",[91,1377,1378],{"class":93,"line":880},[91,1379,1280],{},[91,1381,1382],{"class":93,"line":886},[91,1383,1222],{},[91,1385,1386],{"class":93,"line":892},[91,1387,136],{"emptyLinePlaceholder":135},[91,1389,1390],{"class":93,"line":898},[91,1391,338],{},[91,1393,1394],{"class":93,"line":904},[91,1395,654],{},[91,1397,1398],{"class":93,"line":910},[91,1399,1400],{},"      \u003Cdiv className='card transaction-form'>\n",[91,1402,1403],{"class":93,"line":916},[91,1404,1405],{},"        \u003Ch2>Add transaction details\u003C\u002Fh2>\n",[91,1407,1408],{"class":93,"line":922},[91,1409,1410],{},"        \u003Cform onSubmit={onSubmit}>\n",[91,1412,1413],{"class":93,"line":927},[91,1414,1415],{},"          \u003Cdiv className='form-group'>\n",[91,1417,1418],{"class":93,"line":932},[91,1419,1420],{},"            \u003Clabel htmlFor='name'>Transaction amount\u003C\u002Flabel>\n",[91,1422,1423],{"class":93,"line":938},[91,1424,1425],{},"            \u003Cinput\n",[91,1427,1428],{"class":93,"line":944},[91,1429,1430],{},"              type='number'\n",[91,1432,1433],{"class":93,"line":949},[91,1434,1435],{},"              name='amount'\n",[91,1437,1438],{"class":93,"line":954},[91,1439,1440],{},"              id='amount'\n",[91,1442,1443],{"class":93,"line":960},[91,1444,1445],{},"              placeholder='Enter the amount'\n",[91,1447,1448],{"class":93,"line":966},[91,1449,1450],{},"              value={formState.amount}\n",[91,1452,1453],{"class":93,"line":972},[91,1454,1455],{},"              onChange={(e) => setInput('amount', e.target.value)}\n",[91,1457,1458],{"class":93,"line":978},[91,1459,1460],{},"            \u002F>\n",[91,1462,1463],{"class":93,"line":984},[91,1464,1465],{},"          \u003C\u002Fdiv>\n",[91,1467,1468],{"class":93,"line":990},[91,1469,1415],{},[91,1471,1472],{"class":93,"line":996},[91,1473,1474],{},"            \u003Clabel htmlFor='comment'>Transaction comment\u003C\u002Flabel>\n",[91,1476,1477],{"class":93,"line":1002},[91,1478,1425],{},[91,1480,1481],{"class":93,"line":1008},[91,1482,1483],{},"              type='text'\n",[91,1485,1486],{"class":93,"line":1014},[91,1487,1488],{},"              name='comment'\n",[91,1490,1491],{"class":93,"line":1020},[91,1492,1493],{},"              id='comment'\n",[91,1495,1496],{"class":93,"line":1026},[91,1497,1498],{},"              placeholder='Transaction comment'\n",[91,1500,1501],{"class":93,"line":1032},[91,1502,1503],{},"              value={formState.comment}\n",[91,1505,1506],{"class":93,"line":1038},[91,1507,1508],{},"              onChange={(e) => setInput('comment', e.target.value)}\n",[91,1510,1511],{"class":93,"line":1044},[91,1512,1460],{},[91,1514,1515],{"class":93,"line":1050},[91,1516,1465],{},[91,1518,1519],{"class":93,"line":1056},[91,1520,1415],{},[91,1522,1523],{"class":93,"line":1062},[91,1524,1525],{},"            \u003Clabel htmlFor='transaction-type'>Transaction type\u003C\u002Flabel>\n",[91,1527,1528],{"class":93,"line":1067},[91,1529,1530],{},"            \u003Cselect\n",[91,1532,1533],{"class":93,"line":1073},[91,1534,1535],{},"              name='transaction-type'\n",[91,1537,1538],{"class":93,"line":1078},[91,1539,1540],{},"              id='transaction-type'\n",[91,1542,1543],{"class":93,"line":1083},[91,1544,1545],{},"              value={formState.type}\n",[91,1547,1549],{"class":93,"line":1548},94,[91,1550,1551],{},"              onChange={(e) => setInput('type', e.target.value)}\n",[91,1553,1555],{"class":93,"line":1554},95,[91,1556,1557],{},"            >\n",[91,1559,1561],{"class":93,"line":1560},96,[91,1562,1563],{},"              {Object.keys(TRANSACTION_TYPES).map((type) => {\n",[91,1565,1567],{"class":93,"line":1566},97,[91,1568,1569],{},"                return (\n",[91,1571,1573],{"class":93,"line":1572},98,[91,1574,1575],{},"                  \u003Coption key={`type-${type}`} value={type}>\n",[91,1577,1579],{"class":93,"line":1578},99,[91,1580,1581],{},"                    {TRANSACTION_TYPES[type]}\n",[91,1583,1585],{"class":93,"line":1584},100,[91,1586,1587],{},"                  \u003C\u002Foption>\n",[91,1589,1591],{"class":93,"line":1590},101,[91,1592,1593],{},"                );\n",[91,1595,1597],{"class":93,"line":1596},102,[91,1598,1599],{},"              })}\n",[91,1601,1603],{"class":93,"line":1602},103,[91,1604,1605],{},"            \u003C\u002Fselect>\n",[91,1607,1609],{"class":93,"line":1608},104,[91,1610,1465],{},[91,1612,1614],{"class":93,"line":1613},105,[91,1615,136],{"emptyLinePlaceholder":135},[91,1617,1619],{"class":93,"line":1618},106,[91,1620,1621],{},"          {message && (\n",[91,1623,1625],{"class":93,"line":1624},107,[91,1626,1627],{},"            \u003Cdiv className={`message-${message.type}`}>{message.text}\u003C\u002Fdiv>\n",[91,1629,1631],{"class":93,"line":1630},108,[91,1632,819],{},[91,1634,1636],{"class":93,"line":1635},109,[91,1637,136],{"emptyLinePlaceholder":135},[91,1639,1641],{"class":93,"line":1640},110,[91,1642,1643],{},"          \u003Cdiv className='card-row'>\n",[91,1645,1647],{"class":93,"line":1646},111,[91,1648,1649],{},"            \u003Cbutton\n",[91,1651,1653],{"class":93,"line":1652},112,[91,1654,1655],{},"              className='btn btn-outlined'\n",[91,1657,1659],{"class":93,"line":1658},113,[91,1660,1661],{},"              disabled={loading}\n",[91,1663,1665],{"class":93,"line":1664},114,[91,1666,1667],{},"              type='button'\n",[91,1669,1671],{"class":93,"line":1670},115,[91,1672,1673],{},"              onClick={() => setFormState(INITIAL_STATE)}\n",[91,1675,1677],{"class":93,"line":1676},116,[91,1678,1557],{},[91,1680,1682],{"class":93,"line":1681},117,[91,1683,1684],{},"              Cancel\n",[91,1686,1688],{"class":93,"line":1687},118,[91,1689,1690],{},"            \u003C\u002Fbutton>\n",[91,1692,1694],{"class":93,"line":1693},119,[91,1695,1696],{},"            \u003Cbutton \n",[91,1698,1700],{"class":93,"line":1699},120,[91,1701,1702],{},"              className='btn btn-primary'\n",[91,1704,1706],{"class":93,"line":1705},121,[91,1707,1661],{},[91,1709,1711],{"class":93,"line":1710},122,[91,1712,1713],{},"              type='submit'\n",[91,1715,1717],{"class":93,"line":1716},123,[91,1718,1557],{},[91,1720,1722],{"class":93,"line":1721},124,[91,1723,1724],{},"              Save\n",[91,1726,1728],{"class":93,"line":1727},125,[91,1729,1690],{},[91,1731,1733],{"class":93,"line":1732},126,[91,1734,1465],{},[91,1736,1738],{"class":93,"line":1737},127,[91,1739,1740],{},"        \u003C\u002Fform>\n",[91,1742,1744],{"class":93,"line":1743},128,[91,1745,1746],{},"      \u003C\u002Fdiv>\n",[91,1748,1750],{"class":93,"line":1749},129,[91,1751,360],{},[91,1753,1755],{"class":93,"line":1754},130,[91,1756,366],{},[91,1758,1760],{"class":93,"line":1759},131,[91,1761,372],{},[15,1763,1764],{},"This is how the page looks",[15,1766,1767],{},[1092,1768],{"alt":1769,"src":1770},"new transaction page","\u002Fimages\u002Fposts\u002Freact-app-with-mongodb-atlas-app-services\u002Fba5aed05-55c2-4ff9-bd46-646b58efb566-b9a7a30090.png",[69,1772,1774],{"id":1773},"utility-functions","Utility functions",[15,1776,1777,1778,1781,1782,1785],{},"Create a new folder called ",[88,1779,1780],{},"utils"," in the ",[88,1783,1784],{},"src"," directory, and add an index.js file inside it. Then add the following utility functions for formatting dates and currency in it. You can change the currency code to your desired currency, or even make it configurable per user.",[81,1787,1789],{"className":291,"code":1788,"language":293,"meta":86,"style":86},"let dateTimeFormatter;\nlet currencyFormatter;\n\nexport const formatDateTime = (dateString) => {\n  if (!dateString) {\n    return '';\n  }\n\n  if (!dateTimeFormatter) {\n    dateTimeFormatter = new Intl.DateTimeFormat('en-US', {\n      year: 'numeric',\n      month: 'short',\n      day: 'numeric',\n      hour: 'numeric',\n      minute: 'numeric',\n    });\n  }\n\n  return dateTimeFormatter.format(new Date(dateString));\n};\n\nexport const formatCurrency = (amount) => {\n  if (!currencyFormatter) {\n    currencyFormatter = new Intl.NumberFormat('en-US', {\n      style: 'currency',\n      currency: 'INR',\n      maximumFractionDigits: 2,\n    });\n  }\n\n  return currencyFormatter.format(amount);\n};\n",[88,1790,1791,1796,1801,1805,1810,1815,1820,1825,1829,1834,1839,1844,1849,1854,1859,1864,1869,1873,1877,1882,1886,1890,1895,1900,1905,1910,1915,1920,1924,1928,1932,1937],{"__ignoreMap":86},[91,1792,1793],{"class":93,"line":94},[91,1794,1795],{},"let dateTimeFormatter;\n",[91,1797,1798],{"class":93,"line":101},[91,1799,1800],{},"let currencyFormatter;\n",[91,1802,1803],{"class":93,"line":107},[91,1804,136],{"emptyLinePlaceholder":135},[91,1806,1807],{"class":93,"line":132},[91,1808,1809],{},"export const formatDateTime = (dateString) => {\n",[91,1811,1812],{"class":93,"line":139},[91,1813,1814],{},"  if (!dateString) {\n",[91,1816,1817],{"class":93,"line":145},[91,1818,1819],{},"    return '';\n",[91,1821,1822],{"class":93,"line":160},[91,1823,1824],{},"  }\n",[91,1826,1827],{"class":93,"line":165},[91,1828,136],{"emptyLinePlaceholder":135},[91,1830,1831],{"class":93,"line":171},[91,1832,1833],{},"  if (!dateTimeFormatter) {\n",[91,1835,1836],{"class":93,"line":251},[91,1837,1838],{},"    dateTimeFormatter = new Intl.DateTimeFormat('en-US', {\n",[91,1840,1841],{"class":93,"line":257},[91,1842,1843],{},"      year: 'numeric',\n",[91,1845,1846],{"class":93,"line":351},[91,1847,1848],{},"      month: 'short',\n",[91,1850,1851],{"class":93,"line":357},[91,1852,1853],{},"      day: 'numeric',\n",[91,1855,1856],{"class":93,"line":363},[91,1857,1858],{},"      hour: 'numeric',\n",[91,1860,1861],{"class":93,"line":369},[91,1862,1863],{},"      minute: 'numeric',\n",[91,1865,1866],{"class":93,"line":375},[91,1867,1868],{},"    });\n",[91,1870,1871],{"class":93,"line":380},[91,1872,1824],{},[91,1874,1875],{"class":93,"line":386},[91,1876,136],{"emptyLinePlaceholder":135},[91,1878,1879],{"class":93,"line":391},[91,1880,1881],{},"  return dateTimeFormatter.format(new Date(dateString));\n",[91,1883,1884],{"class":93,"line":397},[91,1885,372],{},[91,1887,1888],{"class":93,"line":403},[91,1889,136],{"emptyLinePlaceholder":135},[91,1891,1892],{"class":93,"line":409},[91,1893,1894],{},"export const formatCurrency = (amount) => {\n",[91,1896,1897],{"class":93,"line":415},[91,1898,1899],{},"  if (!currencyFormatter) {\n",[91,1901,1902],{"class":93,"line":421},[91,1903,1904],{},"    currencyFormatter = new Intl.NumberFormat('en-US', {\n",[91,1906,1907],{"class":93,"line":427},[91,1908,1909],{},"      style: 'currency',\n",[91,1911,1912],{"class":93,"line":433},[91,1913,1914],{},"      currency: 'INR',\n",[91,1916,1917],{"class":93,"line":439},[91,1918,1919],{},"      maximumFractionDigits: 2,\n",[91,1921,1922],{"class":93,"line":444},[91,1923,1868],{},[91,1925,1926],{"class":93,"line":450},[91,1927,1824],{},[91,1929,1930],{"class":93,"line":455},[91,1931,136],{"emptyLinePlaceholder":135},[91,1933,1934],{"class":93,"line":726},[91,1935,1936],{},"  return currencyFormatter.format(amount);\n",[91,1938,1939],{"class":93,"line":731},[91,1940,372],{},[15,1942,1943,1944,1947],{},"This is my current project folder structure after making the above changes. I've removed the ",[88,1945,1946],{},"logo.svg"," file from the project.",[15,1949,1950],{},[1092,1951],{"alt":1952,"src":1953},"current project folder structure","\u002Fimages\u002Fposts\u002Freact-app-with-mongodb-atlas-app-services\u002F44a18f14-8630-41d8-b3a8-36fd0012eeeb-9c4d2470c9.png",[10,1955,1957],{"id":1956},"the-backend","The Backend",[15,1959,1960,1961,1966,1967,1970],{},"First of all, we'll create a new cluster on MongoDB Atlas. For our current purposes, an M0 FREE shared cluster is fine. If this is your first time interacting with MongoDB Atlas, you can use ",[479,1962,1965],{"href":1963,"rel":1964},"https:\u002F\u002Fwww.mongodb.com\u002Fdocs\u002Fguides\u002Fatlas\u002Faccount\u002F",[483],"this excellent guide"," to get started (you can follow till the ",[88,1968,1969],{},"\"Configure a Network Connection\""," section).",[69,1972,1974],{"id":1973},"database-and-collections","Database and collections",[15,1976,1977,1978,1981],{},"Now let's create a database, and add ",[88,1979,1980],{},"transactions"," collection to it.",[15,1983,1984],{},[1092,1985],{"alt":1986,"src":1987},"Create database page","\u002Fimages\u002Fposts\u002Freact-app-with-mongodb-atlas-app-services\u002F7e169c3d-8934-4578-8cae-123fb1c2dd88-3a6c92e175.png",[15,1989,1990],{},[1092,1991],{"alt":1992,"src":1993},"create database and collection","\u002Fimages\u002Fposts\u002Freact-app-with-mongodb-atlas-app-services\u002Fb53585b0-9725-4a7f-9f6d-ab8dd775d498-7d73ffcea4.png",[15,1995,1996,1997,2000],{},"Add one more collection called ",[88,1998,1999],{},"\"users\""," to the database",[15,2002,2003],{},[1092,2004],{"alt":2005,"src":2006},"create users collection","\u002Fimages\u002Fposts\u002Freact-app-with-mongodb-atlas-app-services\u002F609d9579-44df-45a1-8533-2b7bdcf0e33a-4003272ac3.png",[69,2008,2010],{"id":2009},"atlas-app-services","Atlas App Services",[15,2012,2013,2014,2017,2018,2021,2022,2025],{},"Head over to the ",[88,2015,2016],{},"App Services"," tab and create a new Atlas App Services application. Click next in the ",[88,2019,2020],{},"Start with an app template"," screen (while ",[88,2023,2024],{},"\"Build your own App\""," is selected, the default).",[15,2027,2028],{},[1092,2029],{"alt":2030,"src":2031},"Create Atlas App Services app","\u002Fimages\u002Fposts\u002Freact-app-with-mongodb-atlas-app-services\u002Fffc73221-92d5-42b7-b237-beeb76e7c798-1e0b4ce09e.png",[15,2033,2034,2035,2038],{},"Give a name to your application, and click on the ",[88,2036,2037],{},"\"Create App Service\""," button.",[15,2040,2041],{},[1092,2042],{"alt":2043,"src":2044},"configure Atlas App Services App","\u002Fimages\u002Fposts\u002Freact-app-with-mongodb-atlas-app-services\u002F34a74faf-fe76-4945-b9a9-b9ffbbab55f9-a868d71881.png",[69,2046,2048],{"id":2047},"authentication","Authentication",[15,2050,2051],{},"Click on Authentication from the left sidebar and enable anonymous auth and save the draft.",[15,2053,2054],{},[1092,2055],{"alt":2056,"src":2057},"enable anonymous auth","\u002Fimages\u002Fposts\u002Freact-app-with-mongodb-atlas-app-services\u002F15cc1f98-8c90-4b77-9dfb-c7b3919bde54-6cd3025afd.png",[15,2059,2060,2061,2064,2065],{},"After making any changes in the App Services application, we need to deploy the changes for it to take effect. Click on ",[88,2062,2063],{},"REVIEW DRAFT & DEPLOY"," button and deploy the change",[88,2066,485],{},[15,2068,2069],{},[1092,2070],{"alt":2071,"src":2072},"deploy changes","\u002Fimages\u002Fposts\u002Freact-app-with-mongodb-atlas-app-services\u002F1e16a637-f948-4f87-b7bd-a710a034b83f-55f8313043.png",[69,2074,2076],{"id":2075},"creating-the-auth-trigger","Creating the Auth Trigger",[15,2078,2079,2080,2082],{},"Now we're ready to create our first trigger. Whenever a user signs up for our application (even if anonymously), we will create a corresponding document in the ",[88,2081,1999],{}," collection in the database.",[15,2084,2085,2086,2089,2090,2038],{},"Click on triggers from the left sidebar, select ",[88,2087,2088],{},"Authentication Triggers"," from the dropdown menu on the top left, and click on ",[88,2091,2092],{},"Add an Authentication Trigger",[15,2094,2095],{},[1092,2096],{"alt":2097,"src":2098},"create auth trigger","\u002Fimages\u002Fposts\u002Freact-app-with-mongodb-atlas-app-services\u002F43444c56-9d30-4e5a-8821-bcafc8855869-b20d66db95.png",[15,2100,2101,2102,2105,2106,2109,2110,2113,2114,2117,2118,2121,2122,2125],{},"For ",[88,2103,2104],{},"Action Type,"," choose ",[88,2107,2108],{},"Create",", from ",[88,2111,2112],{},"Providers"," dropdown pick ",[88,2115,2116],{},"Anonymous",", and select ",[88,2119,2120],{},"function"," as the ",[88,2123,2124],{},"Event Type",". Doing this allows us to automatically trigger a function whenever a new user signs up for our application.",[15,2127,2128],{},[1092,2129],{"alt":2130,"src":2131},"Configure auth trigger","\u002Fimages\u002Fposts\u002Freact-app-with-mongodb-atlas-app-services\u002F4c61e0ff-29c5-499c-a60f-8476a7665515-f40965d8ed.png",[15,2133,2134,2135,2138,2139,2141],{},"Add the following code in the code panel on the same screen. Don't forget to replace the ",[88,2136,2137],{},"\u003Cdb_name>"," with your database name. What we do here is, from the incoming auth event get the auth user id, and create an entry in the ",[88,2140,1999],{}," collection with some default values.",[81,2143,2145],{"className":291,"code":2144,"language":293,"meta":86,"style":86},"exports = async function(authEvent) {\n  const { user, time } = authEvent;\n\n  const mongoDb = context.services.get('mongodb-atlas').db('\u003Cdb_name>');\n  const usersCollection = mongoDb.collection('users');\n\n  const userData = {\n    _id: BSON.ObjectId(user.id),\n    balance: 0,\n    currMonth: {\n      in: 0,\n      out: 0,\n    },\n    createdAt: time, \n    updatedAt: time,\n  };\n\n  const res = await usersCollection.insertOne(userData);\n  console.log('result of user insert op: ', JSON.stringify(res));\n};\n",[88,2146,2147,2152,2157,2161,2166,2171,2175,2180,2185,2190,2195,2200,2205,2210,2215,2220,2224,2228,2233,2238],{"__ignoreMap":86},[91,2148,2149],{"class":93,"line":94},[91,2150,2151],{},"exports = async function(authEvent) {\n",[91,2153,2154],{"class":93,"line":101},[91,2155,2156],{},"  const { user, time } = authEvent;\n",[91,2158,2159],{"class":93,"line":107},[91,2160,136],{"emptyLinePlaceholder":135},[91,2162,2163],{"class":93,"line":132},[91,2164,2165],{},"  const mongoDb = context.services.get('mongodb-atlas').db('\u003Cdb_name>');\n",[91,2167,2168],{"class":93,"line":139},[91,2169,2170],{},"  const usersCollection = mongoDb.collection('users');\n",[91,2172,2173],{"class":93,"line":145},[91,2174,136],{"emptyLinePlaceholder":135},[91,2176,2177],{"class":93,"line":160},[91,2178,2179],{},"  const userData = {\n",[91,2181,2182],{"class":93,"line":165},[91,2183,2184],{},"    _id: BSON.ObjectId(user.id),\n",[91,2186,2187],{"class":93,"line":171},[91,2188,2189],{},"    balance: 0,\n",[91,2191,2192],{"class":93,"line":251},[91,2193,2194],{},"    currMonth: {\n",[91,2196,2197],{"class":93,"line":257},[91,2198,2199],{},"      in: 0,\n",[91,2201,2202],{"class":93,"line":351},[91,2203,2204],{},"      out: 0,\n",[91,2206,2207],{"class":93,"line":357},[91,2208,2209],{},"    },\n",[91,2211,2212],{"class":93,"line":363},[91,2213,2214],{},"    createdAt: time, \n",[91,2216,2217],{"class":93,"line":369},[91,2218,2219],{},"    updatedAt: time,\n",[91,2221,2222],{"class":93,"line":375},[91,2223,1222],{},[91,2225,2226],{"class":93,"line":380},[91,2227,136],{"emptyLinePlaceholder":135},[91,2229,2230],{"class":93,"line":386},[91,2231,2232],{},"  const res = await usersCollection.insertOne(userData);\n",[91,2234,2235],{"class":93,"line":391},[91,2236,2237],{},"  console.log('result of user insert op: ', JSON.stringify(res));\n",[91,2239,2240],{"class":93,"line":397},[91,2241,372],{},[15,2243,2244,2245,2248],{},"Afterwards, deploy your changes for them to take effect. Should you need to make any changes to the function code, you can do so by clicking the ",[88,2246,2247],{},"Functions"," menu item from the left sidebar and then click on your function name.",[69,2250,2252],{"id":2251},"testing-the-auth-trigger","Testing the Auth Trigger",[15,2254,2255,2256,2259,2260,485],{},"Now we're ready to test the Auth trigger we created in the last section. Before doing that we'll pull the App Services application to our local machine. It is not mandatory to do so, but having the functions' code on the local machine, makes it easier to make changes. Let's install the ",[88,2257,2258],{},"\"realm-cli\""," and configure it ",[479,2261,2264],{"href":2262,"rel":2263},"https:\u002F\u002Fwww.mongodb.com\u002Fdocs\u002Fatlas\u002Fapp-services\u002Fcli\u002F",[483],"using this guide",[15,2266,2267],{},"Now pull the application code by firing up a terminal, navigate to the project's root directory and run the following command.",[81,2269,2271],{"className":83,"code":2270,"language":85,"meta":86,"style":86},"# This will pull the application to the backend \n# folder (the folder will be created automatically)\n# Also, don't forget to use your app_id\nrealm-cli pull --local backend\u002F --remote \u003Capp_id>\n",[88,2272,2273,2278,2283,2288],{"__ignoreMap":86},[91,2274,2275],{"class":93,"line":94},[91,2276,2277],{"class":97},"# This will pull the application to the backend \n",[91,2279,2280],{"class":93,"line":101},[91,2281,2282],{"class":97},"# folder (the folder will be created automatically)\n",[91,2284,2285],{"class":93,"line":107},[91,2286,2287],{"class":97},"# Also, don't forget to use your app_id\n",[91,2289,2290,2293,2296,2299,2302,2305,2309,2312,2315],{"class":93,"line":132},[91,2291,2292],{"class":110},"realm-cli",[91,2294,2295],{"class":118}," pull",[91,2297,2298],{"class":114}," --local",[91,2300,2301],{"class":118}," backend\u002F",[91,2303,2304],{"class":114}," --remote",[91,2306,2308],{"class":2307},"szBVR"," \u003C",[91,2310,2311],{"class":118},"app_i",[91,2313,2314],{"class":122},"d",[91,2316,2317],{"class":2307},">\n",[15,2319,2320,2321,2324,2325,2328],{},"Now go to the ",[88,2322,2323],{},"client"," folder, and install the ",[88,2326,2327],{},"realm-web"," SDK.",[81,2330,2332],{"className":83,"code":2331,"language":85,"meta":86,"style":86},"# From project root run the following\ncd client && yarn add realm-web\n\n# Make a new file for handling realm auth etc\ntouch src\u002FRealmApp.js\n",[88,2333,2334,2339,2355,2359,2364],{"__ignoreMap":86},[91,2335,2336],{"class":93,"line":94},[91,2337,2338],{"class":97},"# From project root run the following\n",[91,2340,2341,2343,2346,2348,2350,2352],{"class":93,"line":101},[91,2342,126],{"class":114},[91,2344,2345],{"class":118}," client",[91,2347,123],{"class":122},[91,2349,148],{"class":110},[91,2351,196],{"class":118},[91,2353,2354],{"class":118}," realm-web\n",[91,2356,2357],{"class":93,"line":107},[91,2358,136],{"emptyLinePlaceholder":135},[91,2360,2361],{"class":93,"line":132},[91,2362,2363],{"class":97},"# Make a new file for handling realm auth etc\n",[91,2365,2366,2368],{"class":93,"line":139},[91,2367,235],{"class":110},[91,2369,2370],{"class":118}," src\u002FRealmApp.js\n",[15,2372,571,2373,2376],{},[88,2374,2375],{},"RealmApp.js"," file",[81,2378,2380],{"className":291,"code":2379,"language":293,"meta":86,"style":86},"import { createContext, useContext, useState, useEffect } from 'react';\nimport * as Realm from 'realm-web';\n\nconst RealmContext = createContext(null);\n\nexport function RealmAppProvider({ appId, children }) {\n  const [realmApp, setRealmApp] = useState(null);\n  const [appDB, setAppDB] = useState(null);\n  const [realmUser, setRealmUser] = useState(null);\n\n  useEffect(() => {\n    setRealmApp(Realm.getApp(appId));\n  }, [appId]);\n\n  useEffect(() => {\n    const init = async () => {\n      if (!realmApp.currentUser) {\n        await realmApp.logIn(Realm.Credentials.anonymous());\n      }\n\n      setRealmUser(realmApp.currentUser);\n      setAppDB(\n        realmApp.currentUser\n          .mongoClient(process.env.REACT_APP_MONGO_SVC_NAME)\n          .db(process.env.REACT_APP_MONGO_DB_NAME)\n      );\n    };\n\n    if (realmApp) {\n      init();\n    }\n  }, [realmApp]);\n\n  return (\n    \u003CRealmContext.Provider value={{ realmUser, appDB }}>\n      {children}\n    \u003C\u002FRealmContext.Provider>\n  );\n}\n\nexport function useRealmApp() {\n  const app = useContext(RealmContext);\n  if (!app) {\n    throw new Error(\n      `No Realm App found. Did you call useRealmApp() inside of a \u003CRealmAppProvider \u002F>.`\n    );\n  }\n\n  return app;\n}\n",[88,2381,2382,2387,2392,2396,2401,2405,2410,2415,2420,2425,2429,2433,2438,2443,2447,2451,2456,2461,2466,2471,2475,2480,2485,2490,2495,2500,2505,2510,2514,2519,2524,2528,2533,2537,2541,2546,2551,2556,2560,2564,2568,2573,2578,2583,2588,2593,2598,2602,2606,2611],{"__ignoreMap":86},[91,2383,2384],{"class":93,"line":94},[91,2385,2386],{},"import { createContext, useContext, useState, useEffect } from 'react';\n",[91,2388,2389],{"class":93,"line":101},[91,2390,2391],{},"import * as Realm from 'realm-web';\n",[91,2393,2394],{"class":93,"line":107},[91,2395,136],{"emptyLinePlaceholder":135},[91,2397,2398],{"class":93,"line":132},[91,2399,2400],{},"const RealmContext = createContext(null);\n",[91,2402,2403],{"class":93,"line":139},[91,2404,136],{"emptyLinePlaceholder":135},[91,2406,2407],{"class":93,"line":145},[91,2408,2409],{},"export function RealmAppProvider({ appId, children }) {\n",[91,2411,2412],{"class":93,"line":160},[91,2413,2414],{},"  const [realmApp, setRealmApp] = useState(null);\n",[91,2416,2417],{"class":93,"line":165},[91,2418,2419],{},"  const [appDB, setAppDB] = useState(null);\n",[91,2421,2422],{"class":93,"line":171},[91,2423,2424],{},"  const [realmUser, setRealmUser] = useState(null);\n",[91,2426,2427],{"class":93,"line":251},[91,2428,136],{"emptyLinePlaceholder":135},[91,2430,2431],{"class":93,"line":257},[91,2432,1231],{},[91,2434,2435],{"class":93,"line":351},[91,2436,2437],{},"    setRealmApp(Realm.getApp(appId));\n",[91,2439,2440],{"class":93,"line":357},[91,2441,2442],{},"  }, [appId]);\n",[91,2444,2445],{"class":93,"line":363},[91,2446,136],{"emptyLinePlaceholder":135},[91,2448,2449],{"class":93,"line":369},[91,2450,1231],{},[91,2452,2453],{"class":93,"line":375},[91,2454,2455],{},"    const init = async () => {\n",[91,2457,2458],{"class":93,"line":380},[91,2459,2460],{},"      if (!realmApp.currentUser) {\n",[91,2462,2463],{"class":93,"line":386},[91,2464,2465],{},"        await realmApp.logIn(Realm.Credentials.anonymous());\n",[91,2467,2468],{"class":93,"line":391},[91,2469,2470],{},"      }\n",[91,2472,2473],{"class":93,"line":397},[91,2474,136],{"emptyLinePlaceholder":135},[91,2476,2477],{"class":93,"line":403},[91,2478,2479],{},"      setRealmUser(realmApp.currentUser);\n",[91,2481,2482],{"class":93,"line":409},[91,2483,2484],{},"      setAppDB(\n",[91,2486,2487],{"class":93,"line":415},[91,2488,2489],{},"        realmApp.currentUser\n",[91,2491,2492],{"class":93,"line":421},[91,2493,2494],{},"          .mongoClient(process.env.REACT_APP_MONGO_SVC_NAME)\n",[91,2496,2497],{"class":93,"line":427},[91,2498,2499],{},"          .db(process.env.REACT_APP_MONGO_DB_NAME)\n",[91,2501,2502],{"class":93,"line":433},[91,2503,2504],{},"      );\n",[91,2506,2507],{"class":93,"line":439},[91,2508,2509],{},"    };\n",[91,2511,2512],{"class":93,"line":444},[91,2513,136],{"emptyLinePlaceholder":135},[91,2515,2516],{"class":93,"line":450},[91,2517,2518],{},"    if (realmApp) {\n",[91,2520,2521],{"class":93,"line":455},[91,2522,2523],{},"      init();\n",[91,2525,2526],{"class":93,"line":726},[91,2527,1280],{},[91,2529,2530],{"class":93,"line":731},[91,2531,2532],{},"  }, [realmApp]);\n",[91,2534,2535],{"class":93,"line":737},[91,2536,136],{"emptyLinePlaceholder":135},[91,2538,2539],{"class":93,"line":743},[91,2540,338],{},[91,2542,2543],{"class":93,"line":749},[91,2544,2545],{},"    \u003CRealmContext.Provider value={{ realmUser, appDB }}>\n",[91,2547,2548],{"class":93,"line":755},[91,2549,2550],{},"      {children}\n",[91,2552,2553],{"class":93,"line":761},[91,2554,2555],{},"    \u003C\u002FRealmContext.Provider>\n",[91,2557,2558],{"class":93,"line":767},[91,2559,366],{},[91,2561,2562],{"class":93,"line":772},[91,2563,447],{},[91,2565,2566],{"class":93,"line":778},[91,2567,136],{"emptyLinePlaceholder":135},[91,2569,2570],{"class":93,"line":784},[91,2571,2572],{},"export function useRealmApp() {\n",[91,2574,2575],{"class":93,"line":789},[91,2576,2577],{},"  const app = useContext(RealmContext);\n",[91,2579,2580],{"class":93,"line":795},[91,2581,2582],{},"  if (!app) {\n",[91,2584,2585],{"class":93,"line":800},[91,2586,2587],{},"    throw new Error(\n",[91,2589,2590],{"class":93,"line":805},[91,2591,2592],{},"      `No Realm App found. Did you call useRealmApp() inside of a \u003CRealmAppProvider \u002F>.`\n",[91,2594,2595],{"class":93,"line":810},[91,2596,2597],{},"    );\n",[91,2599,2600],{"class":93,"line":816},[91,2601,1824],{},[91,2603,2604],{"class":93,"line":822},[91,2605,136],{"emptyLinePlaceholder":135},[91,2607,2608],{"class":93,"line":827},[91,2609,2610],{},"  return app;\n",[91,2612,2613],{"class":93,"line":833},[91,2614,447],{},[15,2616,2617,2618,2620],{},"Modify the ",[88,2619,287],{}," file and add the following changes to it",[81,2622,2624],{"className":291,"code":2623,"language":293,"meta":86,"style":86},"\u002F\u002F Add the RealmApp import\nimport { RealmAppProvider } from '.\u002FRealmApp';\n\n\u002F\u002F Wrap the BrowserRouter inside the RealmAppProvider\nfunction App() {\n  return (\n    \u003CRealmAppProvider appId={process.env.REACT_APP_REALM_APP_ID}>\n      \u003CBrowserRouter>\n        \u003CRoutes>\n          \u003CRoute path='\u002F' element={\u003CLayout \u002F>}>\n            \u003CRoute index element={\u003CDashboard \u002F>} \u002F>\n            \u003CRoute path='\u002Fnew' element={\u003CNewTransaction \u002F>} \u002F>\n          \u003C\u002FRoute>\n        \u003C\u002FRoutes>\n      \u003C\u002FBrowserRouter>\n    \u003C\u002FRealmAppProvider>\n  );\n}\n",[88,2625,2626,2631,2636,2640,2645,2649,2653,2658,2663,2668,2673,2678,2683,2688,2693,2698,2703,2707],{"__ignoreMap":86},[91,2627,2628],{"class":93,"line":94},[91,2629,2630],{},"\u002F\u002F Add the RealmApp import\n",[91,2632,2633],{"class":93,"line":101},[91,2634,2635],{},"import { RealmAppProvider } from '.\u002FRealmApp';\n",[91,2637,2638],{"class":93,"line":107},[91,2639,136],{"emptyLinePlaceholder":135},[91,2641,2642],{"class":93,"line":132},[91,2643,2644],{},"\u002F\u002F Wrap the BrowserRouter inside the RealmAppProvider\n",[91,2646,2647],{"class":93,"line":139},[91,2648,383],{},[91,2650,2651],{"class":93,"line":145},[91,2652,338],{},[91,2654,2655],{"class":93,"line":160},[91,2656,2657],{},"    \u003CRealmAppProvider appId={process.env.REACT_APP_REALM_APP_ID}>\n",[91,2659,2660],{"class":93,"line":165},[91,2661,2662],{},"      \u003CBrowserRouter>\n",[91,2664,2665],{"class":93,"line":171},[91,2666,2667],{},"        \u003CRoutes>\n",[91,2669,2670],{"class":93,"line":251},[91,2671,2672],{},"          \u003CRoute path='\u002F' element={\u003CLayout \u002F>}>\n",[91,2674,2675],{"class":93,"line":257},[91,2676,2677],{},"            \u003CRoute index element={\u003CDashboard \u002F>} \u002F>\n",[91,2679,2680],{"class":93,"line":351},[91,2681,2682],{},"            \u003CRoute path='\u002Fnew' element={\u003CNewTransaction \u002F>} \u002F>\n",[91,2684,2685],{"class":93,"line":357},[91,2686,2687],{},"          \u003C\u002FRoute>\n",[91,2689,2690],{"class":93,"line":363},[91,2691,2692],{},"        \u003C\u002FRoutes>\n",[91,2694,2695],{"class":93,"line":369},[91,2696,2697],{},"      \u003C\u002FBrowserRouter>\n",[91,2699,2700],{"class":93,"line":375},[91,2701,2702],{},"    \u003C\u002FRealmAppProvider>\n",[91,2704,2705],{"class":93,"line":380},[91,2706,366],{},[91,2708,2709],{"class":93,"line":386},[91,2710,447],{},[15,2712,2713,2714,2717],{},"Create an env file named ",[88,2715,2716],{},".env"," at the root of the react project and add the following keys and their respective values",[81,2719,2723],{"className":2720,"code":2721,"language":2722,"meta":86,"style":86},"language-ini shiki shiki-themes github-light github-dark","REACT_APP_REALM_APP_ID=\u003Crealm_app_id>\nREACT_APP_MONGO_SVC_NAME=mongodb-atlas\nREACT_APP_MONGO_DB_NAME=\u003Cdb_name>\n","ini",[88,2724,2725,2730,2735],{"__ignoreMap":86},[91,2726,2727],{"class":93,"line":94},[91,2728,2729],{},"REACT_APP_REALM_APP_ID=\u003Crealm_app_id>\n",[91,2731,2732],{"class":93,"line":101},[91,2733,2734],{},"REACT_APP_MONGO_SVC_NAME=mongodb-atlas\n",[91,2736,2737],{"class":93,"line":107},[91,2738,2739],{},"REACT_APP_MONGO_DB_NAME=\u003Cdb_name>\n",[15,2741,2742],{},"At this point, the project structure looks like the following",[15,2744,2745],{},[277,2746,2747],{},"The client folder",[15,2749,2750],{},[1092,2751],{"alt":2752,"src":2753},"client folder structure","\u002Fimages\u002Fposts\u002Freact-app-with-mongodb-atlas-app-services\u002Ffb9ab78d-a253-46fe-8f9e-f33571dbcf7d-fccc4edb63.png",[15,2755,2756],{},[277,2757,2758],{},"The backend folder",[15,2760,2761],{},[1092,2762],{"alt":2763,"src":2764},"the backend folder structure","\u002Fimages\u002Fposts\u002Freact-app-with-mongodb-atlas-app-services\u002F825fcf67-d0ab-40f8-b7ea-4b713a46bd28-b25118dc19.png",[15,2766,2767,2768,2770,2771,2774,2775,2778],{},"Restart the dev server for the env values to take effect. As soon as the application loads in your browser, an anonymous user should have been created in the realm app. You can check it by going to your ",[88,2769,2016],{}," dashboard, and clicking ",[88,2772,2773],{},"App Users"," from the left sidebar menu. To view the function logs, we can click on ",[88,2776,2777],{},"\"Logs\""," from the same sidebar menu.",[15,2780,2781],{},[1092,2782],{"alt":2783,"src":2784},"app services users","\u002Fimages\u002Fposts\u002Freact-app-with-mongodb-atlas-app-services\u002F696996f4-f283-429b-8bee-d8f059241eff-f256a44435.png",[15,2786,2787,2788,2791],{},"Also, if you go the ",[88,2789,2790],{},"\"Data Services\""," tab, and browse your database's users collection, you should see an entry there. This was created by the Auth Trigger we had created earlier.",[15,2793,2794],{},[1092,2795],{"alt":2796,"src":2797},"user in the users collection of the database","\u002Fimages\u002Fposts\u002Freact-app-with-mongodb-atlas-app-services\u002F45558c99-8c74-4a1b-b29f-faa1731f95f5-825fdbc29c.png",[15,2799,2800],{},"Congratulations are in order. You've made it this far, you were able to create a trigger, and make it work successfully :-).",[69,2802,2804],{"id":2803},"database-interactions-from-the-client","Database interactions from the client",[15,2806,2807,2808,2810],{},"For interacting with our database through the Realm SDK, we need to define the data access rules first. Without that we won't be able to read or write to the database. You can verify this by doing the following changes to our ",[88,2809,574],{}," file.",[81,2812,2814],{"className":291,"code":2813,"language":293,"meta":86,"style":86},"\u002F\u002F Add the following import\nimport { useRealmApp } from '..\u002FRealmApp';\n\nexport const Dashboard = () => {\n  \u002F\u002F ...\n\n  \u002F\u002F Add the following before the return statement\n  const { appDB } = useRealmApp();\n\n  useEffect(() => {\n    const getUser = async () => {\n      const res = await appDB.collection('users').find({});\n      console.log('got some user', res);\n    };\n\n    if (appDB) {\n      getUser();\n    }\n  }, [appDB]);\n\n  \u002F\u002F ...\n}\n",[88,2815,2816,2821,2826,2830,2834,2839,2843,2848,2853,2857,2861,2866,2871,2876,2880,2884,2889,2894,2898,2903,2907,2911],{"__ignoreMap":86},[91,2817,2818],{"class":93,"line":94},[91,2819,2820],{},"\u002F\u002F Add the following import\n",[91,2822,2823],{"class":93,"line":101},[91,2824,2825],{},"import { useRealmApp } from '..\u002FRealmApp';\n",[91,2827,2828],{"class":93,"line":107},[91,2829,136],{"emptyLinePlaceholder":135},[91,2831,2832],{"class":93,"line":132},[91,2833,617],{},[91,2835,2836],{"class":93,"line":139},[91,2837,2838],{},"  \u002F\u002F ...\n",[91,2840,2841],{"class":93,"line":145},[91,2842,136],{"emptyLinePlaceholder":135},[91,2844,2845],{"class":93,"line":160},[91,2846,2847],{},"  \u002F\u002F Add the following before the return statement\n",[91,2849,2850],{"class":93,"line":165},[91,2851,2852],{},"  const { appDB } = useRealmApp();\n",[91,2854,2855],{"class":93,"line":171},[91,2856,136],{"emptyLinePlaceholder":135},[91,2858,2859],{"class":93,"line":251},[91,2860,1231],{},[91,2862,2863],{"class":93,"line":257},[91,2864,2865],{},"    const getUser = async () => {\n",[91,2867,2868],{"class":93,"line":351},[91,2869,2870],{},"      const res = await appDB.collection('users').find({});\n",[91,2872,2873],{"class":93,"line":357},[91,2874,2875],{},"      console.log('got some user', res);\n",[91,2877,2878],{"class":93,"line":363},[91,2879,2509],{},[91,2881,2882],{"class":93,"line":369},[91,2883,136],{"emptyLinePlaceholder":135},[91,2885,2886],{"class":93,"line":375},[91,2887,2888],{},"    if (appDB) {\n",[91,2890,2891],{"class":93,"line":380},[91,2892,2893],{},"      getUser();\n",[91,2895,2896],{"class":93,"line":386},[91,2897,1280],{},[91,2899,2900],{"class":93,"line":391},[91,2901,2902],{},"  }, [appDB]);\n",[91,2904,2905],{"class":93,"line":397},[91,2906,136],{"emptyLinePlaceholder":135},[91,2908,2909],{"class":93,"line":403},[91,2910,2838],{},[91,2912,2913],{"class":93,"line":409},[91,2914,447],{},[15,2916,2917],{},"After saving the code if you try to get the user from the database you'll get the following error message",[81,2919,2921],{"className":291,"code":2920,"language":293,"meta":86,"style":86},"no rule exists for namespace '\u003Cyour_db_name>.users'\n",[88,2922,2923],{"__ignoreMap":86},[91,2924,2925],{"class":93,"line":94},[91,2926,2920],{},[15,2928,2929,2930,2933],{},"Then how could the user entry creation in the database get through earlier, you might ask? Well, the backend triggers are run as the system, so it has the required privileges. You can verify whether the triggers run as ",[88,2931,2932],{},"\"system\""," or not by adding the following console log to the auth trigger function code.",[81,2935,2937],{"className":291,"code":2936,"language":293,"meta":86,"style":86},"console.log('context.user.type:', context.user.type)\n",[88,2938,2939],{"__ignoreMap":86},[91,2940,2941],{"class":93,"line":94},[91,2942,2936],{},[15,2944,2945,2946,2949,2950,2953,2954,2956,2957,2960],{},"Also, the rules we're talking about here are for the Realm App (used by the React App) to access the database on our behalf. Click on ",[88,2947,2948],{},"Rules"," from the sidebar under ",[88,2951,2952],{},"DATA ACCESS"," menu section. Then click on the ",[88,2955,1999],{}," collection in the middle panel, and click on ",[88,2958,2959],{},"Skip (start from scratch)"," at the bottom of the rightmost panel.",[15,2962,2963],{},[1092,2964],{"alt":2965,"src":2966},"add data access rules","\u002Fimages\u002Fposts\u002Freact-app-with-mongodb-atlas-app-services\u002F1ccec609-e70c-4cac-a283-5671df525beb-9811dfa60d.png",[15,2968,2969,2970,2973,2974,2977,2978,2981],{},"Give this rule a proper name, say ReadWriteOwn, and click on ",[88,2971,2972],{},"Advanced Document Filters",", and add the following ",[88,2975,2976],{},"JSON"," expression to both the read and the write text boxes. Select ",[88,2979,2980],{},"Read and write all fields"," from the dropdown menu at the bottom, save the draft, and then deploy your changes.",[81,2983,2987],{"className":2984,"code":2985,"language":2986,"meta":86,"style":86},"language-json shiki shiki-themes github-light github-dark","{\n    \"_id\": {\n        \"%stringToOid\": \"%%user.id\"\n    }\n}\n","json",[88,2988,2989,2994,3002,3013,3017],{"__ignoreMap":86},[91,2990,2991],{"class":93,"line":94},[91,2992,2993],{"class":122},"{\n",[91,2995,2996,2999],{"class":93,"line":101},[91,2997,2998],{"class":114},"    \"_id\"",[91,3000,3001],{"class":122},": {\n",[91,3003,3004,3007,3010],{"class":93,"line":107},[91,3005,3006],{"class":114},"        \"%stringToOid\"",[91,3008,3009],{"class":122},": ",[91,3011,3012],{"class":118},"\"%%user.id\"\n",[91,3014,3015],{"class":93,"line":132},[91,3016,1280],{"class":122},[91,3018,3019],{"class":93,"line":139},[91,3020,447],{"class":122},[15,3022,3023],{},[1092,3024],{"alt":3025,"src":3026},"Add read \u002F write checks for authroization","\u002Fimages\u002Fposts\u002Freact-app-with-mongodb-atlas-app-services\u002F1c72645a-6d5c-4377-a41c-2bb1b0bbaf86-689fda4ee5.png",[15,3028,3029,3030,3033,3034,3037,3038,3041,3042,3045,3046,3048,3049,3051],{},"What we're doing above is: matching the incoming userId (from the HTTP request that the Realm SDK makes) against the ",[88,3031,3032],{},"_id","(which is the document owner's user id ) of the document. If both are the same, the user making the request will be authorized to read\u002Fwrite, else the access will be denied. This is necessary if we don't want any unauthorized access to our data, which is true in this case. Also, ",[88,3035,3036],{},"\"%stringToOid\": \"%%user.id\""," converts the incoming user id (which is a ",[88,3039,3040],{},"string",") to the mongo ",[88,3043,3044],{},"ObjectId"," so that we can compare it against ",[88,3047,3032],{}," (which is an ",[88,3050,3044],{},").",[15,3053,3054],{},"Now if you reload the dashboard page, you can verify that the user data is getting returned successfully from the database through the console log we've added there.",[69,3056,3058],{"id":3057},"add-transactions","Add Transactions",[15,3060,3061,3062,3065],{},"We're ready to create transactions now. Let's make the same data access rules for the ",[88,3063,3064],{},"\"transactions\""," collection with two minor changes.",[29,3067,3068,3080],{},[32,3069,3070,3071,3073,3074,3077,3078,3051],{},"Users should be able to create new transactions on their own, so we need to allow inserting new documents into the collection (as opposed to the ",[88,3072,1999],{}," collection where the insert happens only once, and that too from the auth trigger). So we need to check\u002Fselect the ",[88,3075,3076],{},"\"insert\""," option (just above the ",[88,3079,2972],{},[32,3081,3082,3083,3086,3087,3089,3090,3093],{},"We'll save the transaction's owner id in a new field ",[88,3084,3085],{},"owner_id"," (which will be a ",[88,3088,3040],{},"), so we don't need to convert the incoming userId to an ObjectId. Use the following for ",[88,3091,3092],{},"\"Advance Document Filters\""," read & write text boxes.",[81,3095,3097],{"className":2984,"code":3096,"language":2986,"meta":86,"style":86},"{\n  \"owner_id\": \"%%user.id\"\n}\n",[88,3098,3099,3103,3112],{"__ignoreMap":86},[91,3100,3101],{"class":93,"line":94},[91,3102,2993],{"class":122},[91,3104,3105,3108,3110],{"class":93,"line":101},[91,3106,3107],{"class":114},"  \"owner_id\"",[91,3109,3009],{"class":122},[91,3111,3012],{"class":118},[91,3113,3114],{"class":93,"line":107},[91,3115,447],{"class":122},[15,3117,3118,3119,3122],{},"Save the draft and deploy your changes. Head over to the ",[88,3120,3121],{},"NewTransaction.js"," file and add the following changes.",[81,3124,3126],{"className":291,"code":3125,"language":293,"meta":86,"style":86},"\u002F\u002F Add the following import statement\nimport { useRealmApp } from '..\u002FRealmApp';\n\n\u002F\u002F Call useRealmApp inside the function component\nconst { appDB, realmUser } = useRealmApp();\n\n\u002F\u002F Replace the onSubmit function with the following\nconst onSubmit = async (e) => {\n    e.preventDefault();\n\n    const amount = parseFloat(formState.amount);\n    const comment = formState.comment.trim();\n\n    if (!amount || !comment || !formState.type) {\n      alert('Please fill in all fields');\n      return;\n    }\n\n    try {\n      const finalData = {\n        amount,\n        comment,\n        type: formState.type,\n        owner_id: realmUser.id,\n        createdAt: new Date(),\n      };\n\n      setLoading(true);\n      const res = await appDB.collection('transactions').insertOne(finalData);\n      console.log('result of insert op', res);\n\n      setFormState(INITIAL_STATE);\n      setMessage({ type: 'success', text: 'Successfully saved the transaction.' });\n    } catch (error) {\n      console.log('failed to save the transaction');\n      setMessage({ type: 'error', text: 'Failed to save the transaction.' });\n    }\n\n    setLoading(false);\n  };\n",[88,3127,3128,3133,3137,3141,3146,3151,3155,3160,3165,3169,3173,3177,3181,3185,3189,3193,3197,3201,3205,3209,3214,3219,3224,3229,3234,3238,3243,3247,3252,3257,3262,3266,3271,3276,3280,3284,3289,3293,3297,3302],{"__ignoreMap":86},[91,3129,3130],{"class":93,"line":94},[91,3131,3132],{},"\u002F\u002F Add the following import statement\n",[91,3134,3135],{"class":93,"line":101},[91,3136,2825],{},[91,3138,3139],{"class":93,"line":107},[91,3140,136],{"emptyLinePlaceholder":135},[91,3142,3143],{"class":93,"line":132},[91,3144,3145],{},"\u002F\u002F Call useRealmApp inside the function component\n",[91,3147,3148],{"class":93,"line":139},[91,3149,3150],{},"const { appDB, realmUser } = useRealmApp();\n",[91,3152,3153],{"class":93,"line":145},[91,3154,136],{"emptyLinePlaceholder":135},[91,3156,3157],{"class":93,"line":160},[91,3158,3159],{},"\u002F\u002F Replace the onSubmit function with the following\n",[91,3161,3162],{"class":93,"line":165},[91,3163,3164],{},"const onSubmit = async (e) => {\n",[91,3166,3167],{"class":93,"line":171},[91,3168,1299],{},[91,3170,3171],{"class":93,"line":251},[91,3172,136],{"emptyLinePlaceholder":135},[91,3174,3175],{"class":93,"line":257},[91,3176,1308],{},[91,3178,3179],{"class":93,"line":351},[91,3180,1313],{},[91,3182,3183],{"class":93,"line":357},[91,3184,136],{"emptyLinePlaceholder":135},[91,3186,3187],{"class":93,"line":363},[91,3188,1322],{},[91,3190,3191],{"class":93,"line":369},[91,3192,1327],{},[91,3194,3195],{"class":93,"line":375},[91,3196,1332],{},[91,3198,3199],{"class":93,"line":380},[91,3200,1280],{},[91,3202,3203],{"class":93,"line":386},[91,3204,136],{"emptyLinePlaceholder":135},[91,3206,3207],{"class":93,"line":391},[91,3208,1345],{},[91,3210,3211],{"class":93,"line":397},[91,3212,3213],{},"      const finalData = {\n",[91,3215,3216],{"class":93,"line":403},[91,3217,3218],{},"        amount,\n",[91,3220,3221],{"class":93,"line":409},[91,3222,3223],{},"        comment,\n",[91,3225,3226],{"class":93,"line":415},[91,3227,3228],{},"        type: formState.type,\n",[91,3230,3231],{"class":93,"line":421},[91,3232,3233],{},"        owner_id: realmUser.id,\n",[91,3235,3236],{"class":93,"line":427},[91,3237,1360],{},[91,3239,3240],{"class":93,"line":433},[91,3241,3242],{},"      };\n",[91,3244,3245],{"class":93,"line":439},[91,3246,136],{"emptyLinePlaceholder":135},[91,3248,3249],{"class":93,"line":444},[91,3250,3251],{},"      setLoading(true);\n",[91,3253,3254],{"class":93,"line":450},[91,3255,3256],{},"      const res = await appDB.collection('transactions').insertOne(finalData);\n",[91,3258,3259],{"class":93,"line":455},[91,3260,3261],{},"      console.log('result of insert op', res);\n",[91,3263,3264],{"class":93,"line":726},[91,3265,136],{"emptyLinePlaceholder":135},[91,3267,3268],{"class":93,"line":731},[91,3269,3270],{},"      setFormState(INITIAL_STATE);\n",[91,3272,3273],{"class":93,"line":737},[91,3274,3275],{},"      setMessage({ type: 'success', text: 'Successfully saved the transaction.' });\n",[91,3277,3278],{"class":93,"line":743},[91,3279,1370],{},[91,3281,3282],{"class":93,"line":749},[91,3283,1375],{},[91,3285,3286],{"class":93,"line":755},[91,3287,3288],{},"      setMessage({ type: 'error', text: 'Failed to save the transaction.' });\n",[91,3290,3291],{"class":93,"line":761},[91,3292,1280],{},[91,3294,3295],{"class":93,"line":767},[91,3296,136],{"emptyLinePlaceholder":135},[91,3298,3299],{"class":93,"line":772},[91,3300,3301],{},"    setLoading(false);\n",[91,3303,3304],{"class":93,"line":778},[91,3305,1222],{},[15,3307,3308],{},"Now try creating a transaction, you should be able to see the created transaction in the database. But the main balance in the user document won't change as we haven't written any trigger for that. Let's remedy that and create our second trigger in the next section.",[69,3310,3312],{"id":3311},"creating-a-database-trigger","Creating a Database Trigger",[15,3314,3315],{},"What we want to do here is: whenever a new transaction is created by the user, we update the main balance as well as the in\u002Fout values for the current month. Remember the user document had the below structure",[81,3317,3319],{"className":291,"code":3318,"language":293,"meta":86,"style":86},"const userData = {\n    _id: BSON.ObjectId(user.id),\n    balance: 0,\n    currMonth: {\n      in: 0,\n      out: 0,\n    },\n    createdAt: time, \n    updatedAt: time,\n};\n",[88,3320,3321,3326,3330,3334,3338,3342,3346,3350,3354,3358],{"__ignoreMap":86},[91,3322,3323],{"class":93,"line":94},[91,3324,3325],{},"const userData = {\n",[91,3327,3328],{"class":93,"line":101},[91,3329,2184],{},[91,3331,3332],{"class":93,"line":107},[91,3333,2189],{},[91,3335,3336],{"class":93,"line":132},[91,3337,2194],{},[91,3339,3340],{"class":93,"line":139},[91,3341,2199],{},[91,3343,3344],{"class":93,"line":145},[91,3345,2204],{},[91,3347,3348],{"class":93,"line":160},[91,3349,2209],{},[91,3351,3352],{"class":93,"line":165},[91,3353,2214],{},[91,3355,3356],{"class":93,"line":171},[91,3357,2219],{},[91,3359,3360],{"class":93,"line":251},[91,3361,372],{},[15,3363,3364],{},"Head over to the App Services Triggers section and create a new database trigger.",[15,3366,3367],{},[1092,3368],{"alt":3369,"src":3370},"creating a database trigger","\u002Fimages\u002Fposts\u002Freact-app-with-mongodb-atlas-app-services\u002Fa6adb894-366d-448a-b28b-c57b6e2ae744-e8323bfcdb.png",[15,3372,3373],{},"Select your cluster and database from the dropdowns, and choose transactions as the collection name. Again select function as the event type and create a new function by giving it an appropriate name.",[15,3375,3376],{},[1092,3377],{"alt":3378,"src":3379},"configuring database trigger","\u002Fimages\u002Fposts\u002Freact-app-with-mongodb-atlas-app-services\u002Fb2ee859d-9149-4e97-af37-d4ab4a2a08fd-de4c1dcaef.png",[15,3381,3382,3383,3385,3386,3389],{},"Add the following code to the code panel for the function. What the code essentially does is: get the inserted document, extract the ",[88,3384,3085],{}," from it, and then update the corresponding user document. We use the ",[88,3387,3388],{},"$inc"," pipeline of mongoDB to increment (or decrement in case of deduction by making the value negative) the respective fields.",[81,3391,3393],{"className":291,"code":3392,"language":293,"meta":86,"style":86},"exports = async function(changeEvent) {\n    const doc = changeEvent.fullDocument;\n    \n    console.log('incoming doc:', JSON.stringify(doc))\n    \n    const filter = { _id: BSON.ObjectId(doc.owner_id) };\n    const update = {\n      $set: { updatedAt: new Date() },\n      $inc: {},\n    };\n  \n    if (doc.type === 'IN') {\n      update.$inc.balance = doc.amount;\n      update.$inc['currMonth.in'] = doc.amount;\n    } else {\n      update.$inc.balance = -doc.amount;\n      update.$inc['currMonth.out'] = doc.amount;\n    }\n  \n    \u002F\u002F Replace the DB name with your db name\n    const usersCollection = context.services\n      .get('mongodb-atlas')\n      .db('\u003Cdb_name>')\n      .collection('users');\n    \n    const res = await usersCollection.updateOne(filter, update);\n    console.log('update op res:', JSON.stringify(res));\n};\n",[88,3394,3395,3400,3405,3410,3415,3419,3424,3429,3434,3439,3443,3448,3453,3458,3463,3468,3473,3478,3482,3486,3491,3496,3501,3506,3511,3515,3520,3525],{"__ignoreMap":86},[91,3396,3397],{"class":93,"line":94},[91,3398,3399],{},"exports = async function(changeEvent) {\n",[91,3401,3402],{"class":93,"line":101},[91,3403,3404],{},"    const doc = changeEvent.fullDocument;\n",[91,3406,3407],{"class":93,"line":107},[91,3408,3409],{},"    \n",[91,3411,3412],{"class":93,"line":132},[91,3413,3414],{},"    console.log('incoming doc:', JSON.stringify(doc))\n",[91,3416,3417],{"class":93,"line":139},[91,3418,3409],{},[91,3420,3421],{"class":93,"line":145},[91,3422,3423],{},"    const filter = { _id: BSON.ObjectId(doc.owner_id) };\n",[91,3425,3426],{"class":93,"line":160},[91,3427,3428],{},"    const update = {\n",[91,3430,3431],{"class":93,"line":165},[91,3432,3433],{},"      $set: { updatedAt: new Date() },\n",[91,3435,3436],{"class":93,"line":171},[91,3437,3438],{},"      $inc: {},\n",[91,3440,3441],{"class":93,"line":251},[91,3442,2509],{},[91,3444,3445],{"class":93,"line":257},[91,3446,3447],{},"  \n",[91,3449,3450],{"class":93,"line":351},[91,3451,3452],{},"    if (doc.type === 'IN') {\n",[91,3454,3455],{"class":93,"line":357},[91,3456,3457],{},"      update.$inc.balance = doc.amount;\n",[91,3459,3460],{"class":93,"line":363},[91,3461,3462],{},"      update.$inc['currMonth.in'] = doc.amount;\n",[91,3464,3465],{"class":93,"line":369},[91,3466,3467],{},"    } else {\n",[91,3469,3470],{"class":93,"line":375},[91,3471,3472],{},"      update.$inc.balance = -doc.amount;\n",[91,3474,3475],{"class":93,"line":380},[91,3476,3477],{},"      update.$inc['currMonth.out'] = doc.amount;\n",[91,3479,3480],{"class":93,"line":386},[91,3481,1280],{},[91,3483,3484],{"class":93,"line":391},[91,3485,3447],{},[91,3487,3488],{"class":93,"line":397},[91,3489,3490],{},"    \u002F\u002F Replace the DB name with your db name\n",[91,3492,3493],{"class":93,"line":403},[91,3494,3495],{},"    const usersCollection = context.services\n",[91,3497,3498],{"class":93,"line":409},[91,3499,3500],{},"      .get('mongodb-atlas')\n",[91,3502,3503],{"class":93,"line":415},[91,3504,3505],{},"      .db('\u003Cdb_name>')\n",[91,3507,3508],{"class":93,"line":421},[91,3509,3510],{},"      .collection('users');\n",[91,3512,3513],{"class":93,"line":427},[91,3514,3409],{},[91,3516,3517],{"class":93,"line":433},[91,3518,3519],{},"    const res = await usersCollection.updateOne(filter, update);\n",[91,3521,3522],{"class":93,"line":439},[91,3523,3524],{},"    console.log('update op res:', JSON.stringify(res));\n",[91,3526,3527],{"class":93,"line":444},[91,3528,372],{},[15,3530,2320,3531,3533],{},[88,3532,574],{}," file and make the following changes to the component code",[81,3535,3537],{"className":291,"code":3536,"language":293,"meta":86,"style":86},"\u002F\u002F import BSON from 'realm-web\nimport { BSON } from 'realm-web';\n\n\u002F\u002F Destructure realmUser also from useRealmApp\nconst { realmUser, appDB } = useRealmApp();\n\n\u002F\u002F Update the useEffect to the following\nuseEffect(() => {\n    const getUser = async () => {\n      const res = await appDB\n        .collection('users')\n        .findOne({ _id: new BSON.ObjectId(realmUser.id) });\n      console.log('got some user', res);\n      setUser(res);\n    };\n\n    const getTransactions = async () => {\n      const res = await appDB.collection('transactions').find({});\n      console.log('got transactions res', res);\n      setTransactions(res);\n      setLoading(false);\n    };\n\n    if (appDB) {\n      getUser();\n      getTransactions();\n    }\n}, [appDB, realmUser]);\n",[88,3538,3539,3544,3549,3553,3558,3563,3567,3572,3577,3581,3586,3591,3596,3600,3605,3609,3613,3618,3623,3628,3633,3638,3642,3646,3650,3654,3659,3663],{"__ignoreMap":86},[91,3540,3541],{"class":93,"line":94},[91,3542,3543],{},"\u002F\u002F import BSON from 'realm-web\n",[91,3545,3546],{"class":93,"line":101},[91,3547,3548],{},"import { BSON } from 'realm-web';\n",[91,3550,3551],{"class":93,"line":107},[91,3552,136],{"emptyLinePlaceholder":135},[91,3554,3555],{"class":93,"line":132},[91,3556,3557],{},"\u002F\u002F Destructure realmUser also from useRealmApp\n",[91,3559,3560],{"class":93,"line":139},[91,3561,3562],{},"const { realmUser, appDB } = useRealmApp();\n",[91,3564,3565],{"class":93,"line":145},[91,3566,136],{"emptyLinePlaceholder":135},[91,3568,3569],{"class":93,"line":160},[91,3570,3571],{},"\u002F\u002F Update the useEffect to the following\n",[91,3573,3574],{"class":93,"line":165},[91,3575,3576],{},"useEffect(() => {\n",[91,3578,3579],{"class":93,"line":171},[91,3580,2865],{},[91,3582,3583],{"class":93,"line":251},[91,3584,3585],{},"      const res = await appDB\n",[91,3587,3588],{"class":93,"line":257},[91,3589,3590],{},"        .collection('users')\n",[91,3592,3593],{"class":93,"line":351},[91,3594,3595],{},"        .findOne({ _id: new BSON.ObjectId(realmUser.id) });\n",[91,3597,3598],{"class":93,"line":357},[91,3599,2875],{},[91,3601,3602],{"class":93,"line":363},[91,3603,3604],{},"      setUser(res);\n",[91,3606,3607],{"class":93,"line":369},[91,3608,2509],{},[91,3610,3611],{"class":93,"line":375},[91,3612,136],{"emptyLinePlaceholder":135},[91,3614,3615],{"class":93,"line":380},[91,3616,3617],{},"    const getTransactions = async () => {\n",[91,3619,3620],{"class":93,"line":386},[91,3621,3622],{},"      const res = await appDB.collection('transactions').find({});\n",[91,3624,3625],{"class":93,"line":391},[91,3626,3627],{},"      console.log('got transactions res', res);\n",[91,3629,3630],{"class":93,"line":397},[91,3631,3632],{},"      setTransactions(res);\n",[91,3634,3635],{"class":93,"line":403},[91,3636,3637],{},"      setLoading(false);\n",[91,3639,3640],{"class":93,"line":409},[91,3641,2509],{},[91,3643,3644],{"class":93,"line":415},[91,3645,136],{"emptyLinePlaceholder":135},[91,3647,3648],{"class":93,"line":421},[91,3649,2888],{},[91,3651,3652],{"class":93,"line":427},[91,3653,2893],{},[91,3655,3656],{"class":93,"line":433},[91,3657,3658],{},"      getTransactions();\n",[91,3660,3661],{"class":93,"line":439},[91,3662,1280],{},[91,3664,3665],{"class":93,"line":444},[91,3666,3667],{},"}, [appDB, realmUser]);\n",[15,3669,3670],{},"We're using the realm user id to get the user data now. Also, we've added the code to fetch the user's transactions. After saving the code, make a couple of transactions to see if everything is working properly (it is better to delete the transactions before the database trigger was created for the Math to add up). You should get a screen like the below screenshot",[15,3672,3673],{},[1092,3674],{"alt":3675,"src":3676},"dashboard with transactions","\u002Fimages\u002Fposts\u002Freact-app-with-mongodb-atlas-app-services\u002Fe82a67a0-4ca2-45d3-bd88-4bc910c3cd9f-54e27c1a96.png",[15,3678,3679],{},"If you observe, you'll see that the transactions are in the order in which they were added to the database. Also, we only want to show transactions for the current month in the dashboard. You can verify this by adding an entry for the last month directly to the database, and then on dashboard refresh that entry also shows up (do note that this also changes the main balances as there is no date\u002Fmonth guard for the insert trigger).",[15,3681,3682],{},"To rectify the above issues, make the following changes to the realm call",[81,3684,3686],{"className":291,"code":3685,"language":293,"meta":86,"style":86},"const date = new Date();\ndate.setDate(1);\ndate.setHours(0, 0, 0, 0);\n\nconst res = await appDB.collection('transactions').find(\n    {\n        createdAt: { $gte: date, $lte: new Date() },\n    },\n    {\n        sort: {\n            createdAt: -1,\n        },\n    }\n);\n",[88,3687,3688,3693,3698,3703,3707,3712,3717,3722,3726,3730,3735,3740,3745,3749],{"__ignoreMap":86},[91,3689,3690],{"class":93,"line":94},[91,3691,3692],{},"const date = new Date();\n",[91,3694,3695],{"class":93,"line":101},[91,3696,3697],{},"date.setDate(1);\n",[91,3699,3700],{"class":93,"line":107},[91,3701,3702],{},"date.setHours(0, 0, 0, 0);\n",[91,3704,3705],{"class":93,"line":132},[91,3706,136],{"emptyLinePlaceholder":135},[91,3708,3709],{"class":93,"line":139},[91,3710,3711],{},"const res = await appDB.collection('transactions').find(\n",[91,3713,3714],{"class":93,"line":145},[91,3715,3716],{},"    {\n",[91,3718,3719],{"class":93,"line":160},[91,3720,3721],{},"        createdAt: { $gte: date, $lte: new Date() },\n",[91,3723,3724],{"class":93,"line":165},[91,3725,2209],{},[91,3727,3728],{"class":93,"line":171},[91,3729,3716],{},[91,3731,3732],{"class":93,"line":251},[91,3733,3734],{},"        sort: {\n",[91,3736,3737],{"class":93,"line":257},[91,3738,3739],{},"            createdAt: -1,\n",[91,3741,3742],{"class":93,"line":351},[91,3743,3744],{},"        },\n",[91,3746,3747],{"class":93,"line":357},[91,3748,1280],{},[91,3750,3751],{"class":93,"line":363},[91,3752,3753],{},");\n",[15,3755,3756,3757,3760,3761,3764],{},"We've added the descending sort order for the matched entries. Also, we're only asking for the documents added on or after day 1 of the current month using the ",[88,3758,3759],{},"$gte"," (greater than or equal to) pipeline. I've added the upper bound till the current time (",[88,3762,3763],{},"$lte"," pipeline, less than or equal to) also, though it is not needed. After making these changes you should get the transaction entries in the correct order, and only for the current month.",[15,3766,3767,3768],{},"Congratulations on creating and making the second type of trigger work. ",[277,3769,3770],{},"👏",[69,3772,3774],{"id":3773},"handling-month-changes","Handling Month Changes",[15,3776,3777,3778,3781,3782,3785],{},"Now the only thing remaining is: what happens when the month changes? Since we only want to show the inflows and outflows for the current month, we need to reset them to 0 on the month change, and this should happen automatically. The way to do this is a ",[88,3779,3780],{},"Scheduled Trigger"," (also known as a ",[88,3783,3784],{},"CRON"," job).",[15,3787,3788,3789,3792,3793,3796,3797,2121,3800,3803,3804,3807,3808,3811,3812,2121,3815,3818],{},"Let's go to the app services dashboard one final time, and click on ",[88,3790,3791],{},"Triggers"," in the left sidebar. Then click on \"",[88,3794,3795],{},"Add a Trigger\""," button, and select ",[88,3798,3799],{},"Scheduled",[88,3801,3802],{},"\"Trigger Type\"",". Change the ",[88,3805,3806],{},"\"Schedule Type\""," to ",[88,3809,3810],{},"Advanced"," and use ",[88,3813,3814],{},"0 0 1 * *",[88,3816,3817],{},"CRON schedule",". You can see the dates with times when the next event will occur. Please note that these times are as per the UTC timezone. You can make appropriate changes in hours & minutes if you want to use other timezones.",[15,3820,3821],{},[1092,3822],{"alt":3823,"src":3824},"creating a scheduled trigger","\u002Fimages\u002Fposts\u002Freact-app-with-mongodb-atlas-app-services\u002F04d0ea05-af88-42c7-9ccc-aa67f075ea6c-d73c06de65.png",[15,3826,3827,3828,3831],{},"Finally select ",[88,3829,3830],{},"Function"," as the event type, and add the following code in the code text box. We just fetch the users who've done any transaction during the last month (the in\u002Fout field(s) would be non-zero), and set them to 0.",[81,3833,3835],{"className":291,"code":3834,"language":293,"meta":86,"style":86},"exports = async function () {\n  const usersCollection = context.services\n    .get('mongodb-atlas')\n    .db('\u003Cdb_name>')\n    .collection('users');\n\n  \u002F\u002F Use the $or pipeline to fetch only those users \n  \u002F\u002F who've any transaction last month \n  const users = await usersCollection.find({\n    \"$or\": [\n      { \"currMonth.in\": { \"$gt\": 0 } },\n      { \"currMonth.out\": { \"$gt\": 0 } }\n    ]\n  }).toArray();\n\n  console.log(`find op users length: ${users.length}`);\n  const bulkOps = [];\n  for (const user of users) {\n    bulkOps.push({\n      updateOne: {\n        filter: { _id: user._id },\n        update: {\n          $set: {\n            updatedAt: new Date(),\n            'currMonth.in': 0,\n            'currMonth.out': 0,\n          },\n        },\n      },\n    });\n  }\n\n  if (bulkOps.length) {\n    await usersCollection.bulkWrite(bulkOps);\n    console.log('after the bulk write ops');\n  }\n};\n",[88,3836,3837,3842,3847,3852,3857,3862,3866,3871,3876,3881,3886,3891,3896,3901,3906,3910,3915,3920,3925,3930,3935,3940,3945,3950,3955,3960,3965,3970,3974,3979,3983,3987,3991,3996,4001,4006,4010],{"__ignoreMap":86},[91,3838,3839],{"class":93,"line":94},[91,3840,3841],{},"exports = async function () {\n",[91,3843,3844],{"class":93,"line":101},[91,3845,3846],{},"  const usersCollection = context.services\n",[91,3848,3849],{"class":93,"line":107},[91,3850,3851],{},"    .get('mongodb-atlas')\n",[91,3853,3854],{"class":93,"line":132},[91,3855,3856],{},"    .db('\u003Cdb_name>')\n",[91,3858,3859],{"class":93,"line":139},[91,3860,3861],{},"    .collection('users');\n",[91,3863,3864],{"class":93,"line":145},[91,3865,136],{"emptyLinePlaceholder":135},[91,3867,3868],{"class":93,"line":160},[91,3869,3870],{},"  \u002F\u002F Use the $or pipeline to fetch only those users \n",[91,3872,3873],{"class":93,"line":165},[91,3874,3875],{},"  \u002F\u002F who've any transaction last month \n",[91,3877,3878],{"class":93,"line":171},[91,3879,3880],{},"  const users = await usersCollection.find({\n",[91,3882,3883],{"class":93,"line":251},[91,3884,3885],{},"    \"$or\": [\n",[91,3887,3888],{"class":93,"line":257},[91,3889,3890],{},"      { \"currMonth.in\": { \"$gt\": 0 } },\n",[91,3892,3893],{"class":93,"line":351},[91,3894,3895],{},"      { \"currMonth.out\": { \"$gt\": 0 } }\n",[91,3897,3898],{"class":93,"line":357},[91,3899,3900],{},"    ]\n",[91,3902,3903],{"class":93,"line":363},[91,3904,3905],{},"  }).toArray();\n",[91,3907,3908],{"class":93,"line":369},[91,3909,136],{"emptyLinePlaceholder":135},[91,3911,3912],{"class":93,"line":375},[91,3913,3914],{},"  console.log(`find op users length: ${users.length}`);\n",[91,3916,3917],{"class":93,"line":380},[91,3918,3919],{},"  const bulkOps = [];\n",[91,3921,3922],{"class":93,"line":386},[91,3923,3924],{},"  for (const user of users) {\n",[91,3926,3927],{"class":93,"line":391},[91,3928,3929],{},"    bulkOps.push({\n",[91,3931,3932],{"class":93,"line":397},[91,3933,3934],{},"      updateOne: {\n",[91,3936,3937],{"class":93,"line":403},[91,3938,3939],{},"        filter: { _id: user._id },\n",[91,3941,3942],{"class":93,"line":409},[91,3943,3944],{},"        update: {\n",[91,3946,3947],{"class":93,"line":415},[91,3948,3949],{},"          $set: {\n",[91,3951,3952],{"class":93,"line":421},[91,3953,3954],{},"            updatedAt: new Date(),\n",[91,3956,3957],{"class":93,"line":427},[91,3958,3959],{},"            'currMonth.in': 0,\n",[91,3961,3962],{"class":93,"line":433},[91,3963,3964],{},"            'currMonth.out': 0,\n",[91,3966,3967],{"class":93,"line":439},[91,3968,3969],{},"          },\n",[91,3971,3972],{"class":93,"line":444},[91,3973,3744],{},[91,3975,3976],{"class":93,"line":450},[91,3977,3978],{},"      },\n",[91,3980,3981],{"class":93,"line":455},[91,3982,1868],{},[91,3984,3985],{"class":93,"line":726},[91,3986,1824],{},[91,3988,3989],{"class":93,"line":731},[91,3990,136],{"emptyLinePlaceholder":135},[91,3992,3993],{"class":93,"line":737},[91,3994,3995],{},"  if (bulkOps.length) {\n",[91,3997,3998],{"class":93,"line":743},[91,3999,4000],{},"    await usersCollection.bulkWrite(bulkOps);\n",[91,4002,4003],{"class":93,"line":749},[91,4004,4005],{},"    console.log('after the bulk write ops');\n",[91,4007,4008],{"class":93,"line":755},[91,4009,1824],{},[91,4011,4012],{"class":93,"line":761},[91,4013,372],{},[15,4015,4016],{},"And we're done. Every month on the first day at midnight UTC, we'll make the in & out for every user 0.",[10,4018,4020],{"id":4019},"conclusion","Conclusion",[15,4022,4023],{},"Congratulations on completing this basic tutorial on using MongoDB Atlas App Services and its triggers, and creating a simple react expense tracker app with it. But don't stop here as you can improve the app further. Below are some of the shortcomings of the app which we just built",[29,4025,4026,4034,4037],{},[32,4027,4028,4029,4033],{},"Our app is prone to the javascript floating point math precision issues. You can use the Decimal BSON type provided by MongoDB to handle it in a better way. See ",[479,4030,1965],{"href":4031,"rel":4032},"https:\u002F\u002Fwww.mongodb.com\u002Fdocs\u002Fmanual\u002Ftutorial\u002Fmodel-monetary-data\u002F",[483]," on this issue.",[32,4035,4036],{},"Our ScheduledJob looks for and updates all the users who've made any transaction in the last month. For smaller apps this is fine, but for apps with a large number of users we can't do this from one function. Atlas App functions have a runtime limitation of 180 seconds which may not be enough to do everything",[32,4038,4039],{},"Right now the scheduled trigger fires at midnight UTC, ideally it should fire in each of the user's timezone, etc.",[15,4041,4042],{},"I hope you work on solving some of these problems. If you've any questions, don't hesitate to leave a comment.",[15,4044,4045,4046,4050],{},"Thanks a lot for following along with this tutorial. I hope you found it useful and were able to gain something from it. Please check out the ",[479,4047,4049],{"href":481,"rel":4048},[483],"final code on GitHub"," for your reference.",[15,4052,4053],{},"Keep adding the bits, only they make a BYTE. :-)",[4055,4056,4057],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}",{"title":86,"searchDepth":101,"depth":101,"links":4059},[4060,4061,4062,4070,4081],{"id":12,"depth":101,"text":13},{"id":20,"depth":101,"text":21},{"id":46,"depth":101,"text":47,"children":4063},[4064,4065,4066,4067,4068,4069],{"id":71,"depth":107,"text":72},{"id":271,"depth":107,"text":272},{"id":461,"depth":107,"text":462},{"id":567,"depth":107,"text":568},{"id":1098,"depth":107,"text":1099},{"id":1773,"depth":107,"text":1774},{"id":1956,"depth":101,"text":1957,"children":4071},[4072,4073,4074,4075,4076,4077,4078,4079,4080],{"id":1973,"depth":107,"text":1974},{"id":2009,"depth":107,"text":2010},{"id":2047,"depth":107,"text":2048},{"id":2075,"depth":107,"text":2076},{"id":2251,"depth":107,"text":2252},{"id":2803,"depth":107,"text":2804},{"id":3057,"depth":107,"text":3058},{"id":3311,"depth":107,"text":3312},{"id":3773,"depth":107,"text":3774},{"id":4019,"depth":101,"text":4020},null,"\u002Fimages\u002Fposts\u002Freact-app-with-mongodb-atlas-app-services\u002Fa6593770b0659f9271a7324d22729ffe-895f261352.jpeg","2023-03-04T15:20:40.814Z",false,"md","cleu42yr200080alaawb9axt7",{},"\u002Freact-app-with-mongodb-atlas-app-services",{"title":5,"description":13},"react-app-with-mongodb-atlas-app-services",[4093,4094,4095,4096,2009],"tutorial","mongodb","reactjs","beginners","-xnY-fimN0_GMrBSS_cZ82Y-pSmyc5remtpWzA_4VzQ",1780470201486]