<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Matej Bačo</title>
    <description>The latest articles on DEV Community by Matej Bačo (@meldiron).</description>
    <link>https://dev.to/meldiron</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F668723%2F32ef52a6-fcad-4389-aa55-8ff1b24c39f0.png</url>
      <title>DEV Community: Matej Bačo</title>
      <link>https://dev.to/meldiron</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/meldiron"/>
    <language>en</language>
    <item>
      <title>Announcing Appwrite Sites: The open source Vercel alternative</title>
      <dc:creator>Matej Bačo</dc:creator>
      <pubDate>Mon, 19 May 2025 08:24:26 +0000</pubDate>
      <link>https://dev.to/appwrite/announcing-appwrite-sites-the-open-source-vercel-alternative-35af</link>
      <guid>https://dev.to/appwrite/announcing-appwrite-sites-the-open-source-vercel-alternative-35af</guid>
      <description>&lt;p&gt;You love using Appwrite to power your backend, but when it’s time to actually &lt;em&gt;ship&lt;/em&gt; your website, you’re bouncing between tools, platforms, and extra accounts. That ends today.&lt;/p&gt;

&lt;p&gt;Introducing &lt;strong&gt;Appwrite&lt;/strong&gt; &lt;strong&gt;Sites.&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;A new Appwrite product that lets you deploy and host your websites and web apps &lt;em&gt;right inside Appwrite&lt;/em&gt;. No more juggling services. No more gluing things together. No more multiple subscriptions. Just build, deploy, and go live. All in one place, and it's 100% open source, the kind that lets you (really) self-host and (really) own your data.&lt;/p&gt;

&lt;h1&gt;
  
  
  The all-in-one cloud platform
&lt;/h1&gt;

&lt;p&gt;Appwrite has always been about giving you the tools you need to build fast, secure, and modern apps. However, while Appwrite has always worked hard to deliver a great backend experience, one big piece was missing: web hosting.&lt;/p&gt;

&lt;p&gt;Until now, you had to rely on external platforms like Vercel or Netlify to get your web app live. That meant extra configs, more integrations, and one more invoice to worry about. With Sites, that gap is gone.&lt;/p&gt;

&lt;p&gt;The best part: &lt;strong&gt;Appwrite is a fully open-source platform to offer both frontend hosting and your entire backend. All under one roof.&lt;/strong&gt; From static sites and SSR apps to databases, authentication, storage, messaging and serverless functions, you can now build, deploy, and scale your entire app stack using just Appwrite.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F068ka9t0hjc7l9jkqen9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F068ka9t0hjc7l9jkqen9.png" alt="Image description" width="800" height="542"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Important Sites features
&lt;/h1&gt;

&lt;p&gt;Building Sites as part of the Appwrite ecosystem was a deliberate choice to deliver a seamless experience from crafting your backend to deploying your web apps and websites. To ensure your usage of the platform feels consistent and robust, we set out to match the high standards of other Appwrite products when developing Sites over the past year. &lt;/p&gt;

&lt;p&gt;We’ve introduced several critical features to elevate the hosting experience. Helping you scale efficiently, keep data secure, and deliver lightning-fast performance.&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s part of Sites?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Static hosting&lt;/strong&gt;: Ideal for single-page applications (SPAs), landing pages, documentation sites, and any project that compiles down to static files.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Server-side rendering (SSR)&lt;/strong&gt;: Full support for frameworks like Flutter, React, Next.js, Nuxt, SvelteKit, Astro, Remix, and more right out of the box.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://dev.todocs/products/sites/deploy-from-git"&gt;Git integrations&lt;/a&gt;&lt;/strong&gt;: Connect your GitHub repository to enable automatic deployments on every push.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deployment previews&lt;/strong&gt;: Get a unique preview URL for each pull request—review, test, and merge with confidence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Global CDN&lt;/strong&gt;: Distribute your content worldwide with a powerful content delivery network to ensure low-latency access from anywhere.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DDoS protection&lt;/strong&gt;: Built-in protection mechanisms to help safeguard your apps from denial-of-service attacks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://dev.todocs/products/network"&gt;The Appwrite Network&lt;/a&gt;&lt;/strong&gt;: Take advantage of a growing number of cloud regions, Points of Presence (PoPs), and edge network capabilities, reducing latency and enhancing performance globally.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://dev.todocs/products/network/dns"&gt;The Appwrite DNS&lt;/a&gt;:&lt;/strong&gt; Appwrite provides a dedicated DNS (Domain Name System) service through its &lt;code&gt;appwrite.zone&lt;/code&gt; nameservers to help you manage domain records for your applications.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All this is managed from your Appwrite Console or CLI, and deployable in both &lt;a href="**https://cloud.appwrite.io/"&gt;**Cloud&lt;/a&gt; and &lt;strong&gt;self-hosted&lt;/strong&gt; environments.&lt;/p&gt;

&lt;h1&gt;
  
  
  Sites templates: One-Click websites
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7527o3dqmgflwjrqy4bf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7527o3dqmgflwjrqy4bf.png" alt="Image description" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To make building your website even easier, we created ready-to-use customizable templates that you can deploy with one click, directly from Appwrite. Because not every project needs custom design, and not every team has the time to build from scratch.&lt;/p&gt;

&lt;p&gt;We partnered with open-source maintainers like Docusaurus, ReactAdmin, and many more to bring you diverse templates. We will continue to add more templates to ensure we have everything you need.&lt;/p&gt;

&lt;p&gt;From polished landing pages to waitlist forms and simple promo sites, Sites templates let you go live faster. Read more on how to get started with &lt;a href="https://dev.to/docs/products/sites/templates"&gt;Sites templates in our docs&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  How to get started with Sites
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq9qesl4uwu5rc38tsnb6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq9qesl4uwu5rc38tsnb6.png" alt="Image description" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Cloud
&lt;/h2&gt;

&lt;p&gt;Getting started with Sites Templates takes just a few clicks:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;In the Appwrite Console's sidebar, click &lt;strong&gt;Sites&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Click on the &lt;strong&gt;Create site&lt;/strong&gt; button.&lt;/li&gt;
&lt;li&gt;After clicking on &lt;strong&gt;Connect Git repository&lt;/strong&gt;, select your repository.&lt;/li&gt;
&lt;li&gt;After connecting to GitHub, (optionally) add a name and site ID.&lt;/li&gt;
&lt;li&gt;Verify that the correct framework is selected.&lt;/li&gt;
&lt;li&gt;Confirm the install command, build command, and output directory in the build settings. To learn more, visit your preferred &lt;a href="https://dev.to/docs/products/sites/quick-start#framework-quick-starts"&gt;framework quick-start&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Add any environment variables required by the site.&lt;/li&gt;
&lt;li&gt;The site will be created, and a build will begin. Once the build is completed, you'll have created your first site. You can use your site's &lt;strong&gt;domain&lt;/strong&gt; to access the deployment.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We have added quick starts for popular frameworks to help you set up faster with your preferred framework, with more to come. As of today you can follow quick starts for &lt;a href="https://dev.to/docs/products/sites/quick-start/flutter"&gt;Flutter&lt;/a&gt;, &lt;a href="https://dev.to/docs/products/sites/quick-start/sveltekit"&gt;Nuxt&lt;/a&gt;, &lt;a href="https://dev.to/docs/products/sites/quick-start/nextjs"&gt;Next.js&lt;/a&gt;, Angular, &lt;a href="https://dev.to/docs/products/sites/quick-start/sveltekit"&gt;SvelteKit&lt;/a&gt;, &lt;a href="https://dev.to/docs/products/sites/quick-start/remix"&gt;Remix&lt;/a&gt;, &lt;a href="https://dev.to/docs/products/sites/quick-start/sveltekit"&gt;Astro&lt;/a&gt;, &lt;a href="https://dev.to/docs/products/sites/quick-start/sveltekit"&gt;Vue.js&lt;/a&gt;, &lt;a href="https://dev.to/docs/products/sites/quick-start/sveltekit"&gt;React&lt;/a&gt;, and &lt;a href="https://dev.to/docs/products/sites/quick-start/sveltekit"&gt;Vanilla.JS&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Self-hosted
&lt;/h2&gt;

&lt;p&gt;If you prefer to use Appwrite Sites open-source version, you can visit the &lt;a href="https://appwrite.io/docs/advanced/self-hosting" rel="noopener noreferrer"&gt;self-hosting documentation&lt;/a&gt; and review the &lt;a href="https://github.com/appwrite/appwrite" rel="noopener noreferrer"&gt;Appwrite repository&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Appwrite Sites pricing
&lt;/h1&gt;

&lt;p&gt;Appwrite Sites is free to use until July 1st, 2025. We will inform you before introducing pricing so that you know well beforehand and have no surprises. &lt;/p&gt;

&lt;h1&gt;
  
  
  One platform to build, host, scale
&lt;/h1&gt;

&lt;p&gt;Like many developer tools, we are here to make you more productive. By bringing hosting into Appwrite, you spend less time on setup and more time on what matters: building. Fewer moving parts means fewer things to break, and everything works seamlessly with your existing Appwrite services like Databases, Functions, Storage, and Auth. It’s Appwrite’s goal to improve your time to production. We make you move faster with &lt;a href="https://dev.to/products/sites"&gt;Sites&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Sites will be available on both &lt;strong&gt;Appwrite Cloud&lt;/strong&gt; and &lt;strong&gt;self-hosted&lt;/strong&gt; deployments.&lt;/p&gt;

&lt;p&gt;Spin up your first site from the Console or &lt;a href="https://dev.to/docs/tooling/command-line/sites"&gt;CLI&lt;/a&gt; and go live in minutes. No more stitching platforms together, no more waiting for deploys, just fast, integrated shipping.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>webdev</category>
    </item>
    <item>
      <title>I Found Perfect CMS after Years of Trial and Error</title>
      <dc:creator>Matej Bačo</dc:creator>
      <pubDate>Thu, 03 Apr 2025 11:28:32 +0000</pubDate>
      <link>https://dev.to/meldiron/i-found-perfect-cms-after-years-of-trial-and-error-2nb8</link>
      <guid>https://dev.to/meldiron/i-found-perfect-cms-after-years-of-trial-and-error-2nb8</guid>
      <description>&lt;p&gt;As a freelancer, I always looked for ways to create admin panels for my clients. It gives them control, and makes me money. It was impossible to find something simple, free, and privacy-friendly. After a fair share of experience with dozens of systems, I finally found one that aligns with my expectations. &lt;/p&gt;

&lt;h2&gt;
  
  
  Finding the Perfect CMS
&lt;/h2&gt;

&lt;p&gt;Every CMS is useful. All of them exist because they have a target audience that is willing to pay, since it provides them value.&lt;/p&gt;

&lt;p&gt;After years of searching, I created a set of rules that define the &lt;em&gt;"Perfect CMS"&lt;/em&gt; for static sites.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Own my data&lt;/strong&gt;. Don't store my content, and don't become a dependency for my projects.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extendable&lt;/strong&gt;. Allow me to add custom components. Ideally open-sourced too, for ease of learning.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Framework agnostic&lt;/strong&gt;. Don't tell me to use Angular. Integrate with any framework, and without a framework.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Free&lt;/strong&gt;. Don't limit the amount of projects, clients, or features. Don't rely on paid users to keep the product alive.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Self-hostable&lt;/strong&gt;. Don't require Cloud for authentication. Don't vendor lock-in access to CMS.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;On top of those points, CMS must be simple to use, quick to implement, and nice-looking. Those all make it easier to convince clients they need a CMS.&lt;/p&gt;

&lt;h3&gt;
  
  
  Choose Wisely, Choose Git-based CMS
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Own my data&lt;/strong&gt;. Don't store my content, and don't become a dependency for my project.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The biggest difference between Git-based CMS and API-based CMS is complexity. Complexity can be beneficial to large blog platforms or e-commerce platforms, but that is not a typical project of freelancers. Much more common is a landing page, personal website, or community platform.&lt;/p&gt;

&lt;p&gt;Simple websites don't benefit from API-based CMS, and get all the downsides. One must use an SDK, or send raw HTTP requests to fetch the data. One must implement caching to make their application quick again. And most importantly, one must have fallback behavior if CMS ever goes into downtime.&lt;/p&gt;

&lt;p&gt;Git-based CMS solves all the pain points above. All data is stored in a file defined by you, whether it's JSON, YAML, TOML, or Markdown. Data is always accessible during the build step, so importing it directly gives you access with code auto-completion, and no delays when rendering the website. Data is stored directly as part of your source code, and doesn't create any direct link with the CMS that is used to edit them.&lt;/p&gt;

&lt;p&gt;A significant downside of using Git-based CMS is the speed at which changes are reflected. Since a re-build is required after each change, it can take a couple of seconds (or up to a few minutes, depending on which framework you use) to apply changes on production. This can be a bit frustrating, but a typical freelancer's client doesn't mind. If one would honestly need changes instantly reflected on a website, they are at size when they benefit from API-based CMS anyway.&lt;/p&gt;

&lt;p&gt;Thankfully, there are many Git-based CMS such as &lt;a href="https://decapcms.org/" rel="noopener noreferrer"&gt;Decap CMS&lt;/a&gt;, &lt;a href="https://tina.io/" rel="noopener noreferrer"&gt;TinaCMS&lt;/a&gt;, or &lt;a href="https://craftercms.com/" rel="noopener noreferrer"&gt;Crafter CMS&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Component Library is Not Enough
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Extendable&lt;/strong&gt;. Allow me to add custom components. Ideally open-sourced too, for ease of learning.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Basic components are enough for the first iteration of your CMS. As client's business grows, so do their demands. Coloring data based on importance, or adding map picker to visually represent location, may sooner or later become highly important for CMS users efficiency.&lt;/p&gt;

&lt;p&gt;When that happens, your CMS should provide you with docs on how to extend it, instead of asking you to submit a feature request. Relying on maintainers is the worst outcome when it comes to custom components in CMS.&lt;/p&gt;

&lt;p&gt;Many providers such as &lt;a href="https://directus.io/" rel="noopener noreferrer"&gt;Directus&lt;/a&gt;, &lt;a href="https://www.appsmith.com/" rel="noopener noreferrer"&gt;Appsmith&lt;/a&gt;, or &lt;a href="https://budibase.com/" rel="noopener noreferrer"&gt;Budibase&lt;/a&gt; all have ways to fully customize the viewing and editing experience of each field.&lt;/p&gt;

&lt;h3&gt;
  
  
  CMS Doesn't Live in My Application
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Framework agnostic&lt;/strong&gt;. Don't tell me to use Angular. Integrate with any framework, and without a framework.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You likely experienced this before. You find an amazing UI library, you decide to use it in your next project, and moments later you realize it only supports Next.js. You find an amazing PDF library to generate great-looking invoices, only to realize it's for PHP. You find ...&lt;/p&gt;

&lt;p&gt;Next.js and PHP are great. But it's my decision if they are great for the project I work on. If CMS provided support only for a specific framework, either you can't use it for every project you build, or you throw yourself into a framework you may not be familiar with, or even worse, it may not be the right choice for the job. CMS is there to assist you, not to throw logs under your feet.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://payloadcms.com/" rel="noopener noreferrer"&gt;Payload&lt;/a&gt;, a CMS powered by Next.js, or &lt;a href="https://github.com/sveltia/sveltia-cms" rel="noopener noreferrer"&gt;Sveltia CMS&lt;/a&gt;, a Decap CMS alternative using Svelte, are examples of CMS that I recommend to avoid until they become framework agnostic.&lt;/p&gt;

&lt;h3&gt;
  
  
  Save Money to Make Money
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Free&lt;/strong&gt;. Don't limit the amount of projects, clients, or features. Don't rely on paid users to keep the product alive.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Limits and free tiers are foundation stones of every commercial platform. It may remain open sourced, or a version 2.0 may be released with cloud-only support. It could remain free for unlimited projects, or it could change the limit to 3 projects over the weekend. It's also a matter of trust whether a Cloud doesn't change pricing from $5 per month to $15.&lt;/p&gt;

&lt;p&gt;When picking a preferred CMS for simple sites, it should not limit the amount of projects, collaborators, fields, or rows. Allowing pricing to affect you eventually creates expenses, which lower your income from long-term clients.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.sanity.io/" rel="noopener noreferrer"&gt;Sanity&lt;/a&gt;, &lt;a href="https://www.contentful.com/" rel="noopener noreferrer"&gt;Contentful&lt;/a&gt;, and &lt;a href="https://hygraph.com/" rel="noopener noreferrer"&gt;Hygraph&lt;/a&gt; are a few examples. Very often a CMS has a pricing page, but is also open-source, and has docs on how to self-host it for free. A great example of that is &lt;a href="https://directus.io/" rel="noopener noreferrer"&gt;Directus&lt;/a&gt;, and there is no need to avoid those CMS.&lt;/p&gt;

&lt;h3&gt;
  
  
  Authentication, a Bait for Headless CMS
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Self-hostable&lt;/strong&gt;. Don't require Cloud for authentication. Don't vendor lock-in access to CMS.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Git-based CMS often provide Cloud authentication to simplify process of setting it up. This is perfect for development, and initial integration to ensure everything works well together. As soon as application goes to production, it's a great risk to keep using the Cloud offering.&lt;/p&gt;

&lt;p&gt;Primary problem is the fact you are authorizing someone's else GitHub application, not yours. This means maintainers of the Cloud offering of the CMS can see contents of your repository, do changes, and possibly even more. This creates potential for security risks, makes app less likely to comply with strict privacy regulations, and can create helpless situation during authentication gateway downtimes. Such Cloud authentication can also sunset at any moment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honorable Mention: Outstatic
&lt;/h2&gt;

&lt;p&gt;Until now, my favourite CMS was &lt;a href="https://outstatic.com/" rel="noopener noreferrer"&gt;Outstatic&lt;/a&gt;, a CMS for Next.js applications. It's fully open source project that can be self-hosted, and focuses on keeping your data inside your git repository. It's simple-looking interface is amazing for any small CMS, and AI integration for text generation can be a great plus for some projects.&lt;/p&gt;

&lt;p&gt;Downside of Outstatic is limited collaboration features, since it doesn't come with database, so only authentication method is GitHub OAuth. This can be limiting, as explaining to clients what GitHub is and why they need the account can be hard. Additionally, it's SDKs tightly couple you to use Next.js.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Pages CMS is the Best CMS
&lt;/h2&gt;

&lt;p&gt;Considering all of the above, the best CMS is &lt;a href="https://pagescms.org/" rel="noopener noreferrer"&gt;Pages CMS&lt;/a&gt;, a modern Git-based CMS with a focus on static sites. With a single configuration file, in matter of minutes, it allows you to manage your content and its media files through an intuitive UI. Pages CMS support all collaboration features you or your client might need including MagicURL login, history of changes, and more. It comes with a responsive design to let your client do changes from their phone.&lt;/p&gt;

&lt;p&gt;Pages CMS keeps all your contents inside your repository, and only requires a single configuration file to be set up. What's even more, it's framework agnostic and can be used with Astro, Next.js, SvelteKit, or any other framework. Because of the nature of Pages CMS, it doesn't integrate with a framework, and only looks at files containing your data such as JSON, YAML, Markdown, or TOML.&lt;/p&gt;

&lt;p&gt;It supports 9 file formats and 13 built-in field types. While this is more than enough, Pages CMS can be forked, self-hosted, and extended with custom components. Doing this requires basic understanding of React, but detailed documentation and existing field types serve as great starting points.&lt;/p&gt;

&lt;p&gt;The entirety of Pages CMS is free and open sourced, including its Cloud platform. Cloud offering has no limits on the amount of collaborators, projects, or content. Developers of Pages CMS even announced &lt;code&gt;Free forever&lt;/code&gt; as part of their &lt;a href="https://github.com/pages-cms/pages-cms/discussions/128" rel="noopener noreferrer"&gt;1.0 release notes&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let's Integrate with Pages CMS
&lt;/h2&gt;

&lt;p&gt;As with any CMS I come across, I try to use it with an application. This time I decided to use &lt;a href="https://github.com/alamguardin/Astrolink" rel="noopener noreferrer"&gt;Astrolink&lt;/a&gt;, a minimal alternative to &lt;a href="https://linktr.ee/" rel="noopener noreferrer"&gt;Linktree&lt;/a&gt;. You can check it out on a &lt;a href="https://astrolink.vercel.app/" rel="noopener noreferrer"&gt;demo page&lt;/a&gt;, or see it's details in &lt;a href="https://astro.build/themes/details/astrolink-template-to-share-about-yourself/" rel="noopener noreferrer"&gt;Astro theme catalog&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa85jl7ml4h7ckefxpi9d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa85jl7ml4h7ckefxpi9d.png" alt="Astrolink" width="800" height="860"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Prepare the Website
&lt;/h2&gt;

&lt;p&gt;Initial observation shows Astrolink already sources all of the page data from a single file &lt;a href="https://github.com/alamguardin/Astrolink/blob/main/src/data/user.json" rel="noopener noreferrer"&gt;src/data/user.json&lt;/a&gt;. This means, Pages CMS will only need access to this file, and I don't need to make any adjustments to source code.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you want to use Pages CMS with your project, and you have your data inside components or JavaScript/TypeScript files, I highly recommend moving data to JSON files first. Many frameworks have ability to import JSON files directly into components. Those that don't can store JSON files in public assets folder, and be loaded with a &lt;code&gt;fetch()&lt;/code&gt; request.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgl526m25zw06djw8k340.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgl526m25zw06djw8k340.png" alt="User.json file" width="800" height="499"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Getting Astrolink live was matter of a few very simple steps: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Fork GitHub repository&lt;/li&gt;
&lt;li&gt;Deploy website to Cloud (&lt;a href="https://vercel.app/" rel="noopener noreferrer"&gt;Vercel&lt;/a&gt;, &lt;a href="https://www.netlify.com/" rel="noopener noreferrer"&gt;Netlify&lt;/a&gt;, or others)&lt;/li&gt;
&lt;li&gt;Connect custom domain using DNS&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Once deployed, it was time to configure Pages CMS.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connect with Pages CMS
&lt;/h2&gt;

&lt;p&gt;To get started with &lt;a href="https://pagescms.org/" rel="noopener noreferrer"&gt;Pages CMS&lt;/a&gt;, I visited their &lt;a href="https://app.pagescms.org/" rel="noopener noreferrer"&gt;Cloud platform&lt;/a&gt;, and signed in with GitHub. Through this process I authorized with Pages CMS OAuth application, and gave their GitHub application access to my repositories, including read and write access.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Pages CMS can be self-hosted &lt;strong&gt;very easily&lt;/strong&gt;, and they have a step-by-step guide on how to do that. I highly recommend self-hosting Pages CMS and authorizing your own GitHub application for security and privacy reasons.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Once my GitHub was connected, I saw my newly forked Astrolink repository in the list of Pages CMS projects.&lt;/p&gt;

&lt;p&gt;The project was not ready to be used yet, because Pages CMS needs a &lt;code&gt;.pages.yml&lt;/code&gt; configuration file that will hold all the Pages CMS configuration. Frankly, I didn't need to know that. A nice-looking error page explained it all, and provided a button to create an empty configuration file to continue.&lt;/p&gt;

&lt;p&gt;Afterwards, I spent a couple of minutes reading &lt;a href="https://pagescms.org/docs/" rel="noopener noreferrer"&gt;Pages CMS documentation&lt;/a&gt;, more specifically &lt;a href="https://pagescms.org/docs/configuration/" rel="noopener noreferrer"&gt;Configuration&lt;/a&gt; and &lt;a href="https://pagescms.org/docs/examples/" rel="noopener noreferrer"&gt;Examples&lt;/a&gt; pages.&lt;/p&gt;

&lt;p&gt;While fields in YAML file were not any standard knowledge developers usually have, it was very easy to understand. I am nowhere near knowing the exact schema by heart, but I already feel confident setting up a configuration file for any kind of data thanks to its highlighting, and schema validation directly in Pages CMS.&lt;/p&gt;

&lt;p&gt;Below you can find configuration file I used for the Astrolink project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;user&lt;/span&gt;
    &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;User&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;src/data/user.json&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;file&lt;/span&gt;
    &lt;span class="na"&gt;fields&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;name&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;profession&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;links&lt;/span&gt;
    &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Links&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;src/data/user.json&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;file&lt;/span&gt;
    &lt;span class="na"&gt;fields&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;links&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;object&lt;/span&gt;
        &lt;span class="na"&gt;list&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="na"&gt;fields&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
         &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;title&lt;/span&gt;
           &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
         &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;description&lt;/span&gt;
           &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
         &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;icon&lt;/span&gt;
           &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Icon names can be found at www.remixicon.com&lt;/span&gt;
           &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
         &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;url&lt;/span&gt;
           &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
           &lt;span class="na"&gt;pattern&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;^(https?:\/\/)?(www\.)?[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}(\/[^\s]*)?$&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As I saved the configuration file, new options appeared in the left side menu. Inside, I found my schema applied in a simple interface of inputs and buttons. What's even more interesting, all data was already loaded, indicating the connection to data stored in &lt;code&gt;src/data/user.json&lt;/code&gt; was successful.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frbikndmz267nv75bccqn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frbikndmz267nv75bccqn.png" alt="Pages CMS UI" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I updated a few texts, and saved the changes. A commit was created automatically, pushed, and a Vercel deployment started. After couple of seconds, I refreshed my website and changes I made were live.&lt;/p&gt;

&lt;h2&gt;
  
  
  Collaboration with Client
&lt;/h2&gt;

&lt;p&gt;Since the main purpose of CMS is to provide the client with a simple interface to update content on their static site, I decided to try the full experience with Pages CMS.&lt;/p&gt;

&lt;p&gt;In collaboration settings, I added a friend of mine to the project through email invitation. They received the invitation in their inbox, and followed steps provided. An amazing feature presented itself when I noticed the sign-in page supports passwordless email login alongside GitHub OAuth. I can't stress enough how important this is since clients almost never have GitHub accounts.&lt;/p&gt;

&lt;p&gt;After a passwordless login using Magic URL, my friend already saw the Astrolink project in their list, and started editing content. No setup was needed from his side.&lt;/p&gt;

&lt;p&gt;Finally, yet another feature amazed me. When my friend started making changes, I saw it all on the right side of Pages CMS, which served as a history of changes. If a client &lt;strong&gt;ever&lt;/strong&gt; does a change that breaks the website, as a developer I can easily see what the change was, when, and within a few clicks I can rollback the changes to make the website stable again. Moments like this make business relations more stable and lucrative.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm15umo1vfmre6wi5ebbz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm15umo1vfmre6wi5ebbz.png" alt="Pages CMS history" width="800" height="587"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Both website and CMS are now live! Within less than hour of finding out about Pages CMS, I was able to deploy my first site with CMS integrated. You can visit my &lt;a href="https://matejbaco.vercel.app/" rel="noopener noreferrer"&gt;Astrolink website&lt;/a&gt;, and I invite you to make yours to try out Pages CMS. Additionally, I recommend you to make picture URL on the website configurable, to gain further experience with Pages CMS. For those looking for challenge, Pages CMS supports media, so you can switch from picture URL to proper drag&amp;amp;drop file input.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pages CMS in Production
&lt;/h2&gt;

&lt;p&gt;As mentioned above, Pages CMS requires read and write access to your repository, which can be scary for multiple reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Security: Pages CMS Cloud has full access to your repository, possibly deleting the entire project.&lt;/li&gt;
&lt;li&gt;Privacy: Pages CMS Cloud has permission to see the source code of your application.&lt;/li&gt;
&lt;li&gt;Billing: Pages CMS Cloud can become paid. They promised it won't, but it can.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While self-hosting doesn't fully address security, it surely makes it more secure. Bugs in code can still cause problems, but the risk is much more manageable.&lt;/p&gt;

&lt;p&gt;Setting up self-hosted Pages CMS looks like complex task since it has many moving parts, but their docs are &lt;strong&gt;amazing&lt;/strong&gt; and provide follow-along instructions to set everything up. The entire setup was made with developers in mind, as it recommends &lt;a href="https://resend.com/" rel="noopener noreferrer"&gt;Resend&lt;/a&gt; and &lt;a href="https://turso.tech/" rel="noopener noreferrer"&gt;Turso&lt;/a&gt;, both Cloud applications that provide free tiers.&lt;/p&gt;

&lt;p&gt;What caught me by surprise was how easy it is to add custom components. From my past experience building CMS for clients, I know simple component changes can make a big difference. &lt;em&gt;For example, simply coloring a date red or green depending on package shipping status can avoid delayed deliveries.&lt;/em&gt; Not only was the process of adding custom components documented, it also linked to over a dozen existing components providing a starting point.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you are interested to gain confidence with custom components, I recommend to build component that stores latitude and longitude of location on a map, and provide interface using &lt;a href="https://developers.google.com/maps" rel="noopener noreferrer"&gt;Google Maps&lt;/a&gt;, &lt;a href="https://www.openstreetmap.org/" rel="noopener noreferrer"&gt;OpenStreetMap&lt;/a&gt;, or similar map provider.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you use Pages CMS in production, and successfully self-hosted the application, it's the best time to consider &lt;a href="https://github.com/sponsors/hunvreus" rel="noopener noreferrer"&gt;sponsoring Pages CMS&lt;/a&gt;. Engineers behind this amazing open-source project are active, and providing them with financial support allows them to develop new features, which can benefit you in the long run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I am finally at peace. No more searching for a better CMS. Pages CMS is my go-to, if I ever need one for sites I build. It provides a nice-looking and simple interface that's quick and easy to setup. It's free, open-source, self-hostable, framework-agnostic, and doesn't lack collaboration features. What more should I ask for.&lt;/p&gt;

&lt;p&gt;If you don't share my passion for Pages CMS, take inspiration from my criteria for the perfect CMS. Many of them make sense in every situation, and help you find a solution that is long-term, and sustainable. If it wasn't clear already, every static site deserves a CMS.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>opensource</category>
      <category>productivity</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Biometric authentication with Passkeys</title>
      <dc:creator>Matej Bačo</dc:creator>
      <pubDate>Sat, 09 Mar 2024 14:54:59 +0000</pubDate>
      <link>https://dev.to/meldiron/biometric-authentication-with-passkeys-3e1</link>
      <guid>https://dev.to/meldiron/biometric-authentication-with-passkeys-3e1</guid>
      <description>&lt;p&gt;Password-based authentication is the most common form of authentication, but it is not the most secure. Ideally, everyone is using a password manager to have a unique and strong password on every website, and everyone protects their account with multi-factor authentication. That's not a reality, sadly. While the website can enforce some best practices for password security, password reusing - a leading problem of internet security - not all can be prevented by developers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction and benefits
&lt;/h2&gt;

&lt;p&gt;Passkeys provide an authentication method that does not require users to remember a password. Instead, to authenticate, a combination of owning a device with a private key and authorizing yourself with fingerprint, face, or voice is needed. Passkeys use public key cryptography technology, which provides security that is incomparable to password-based authentication. If a server or a database is ever compromised, the user's passkeys remain secure since there is no secret stored on the server. This makes server breaches ineffective to passkey authentication. The nature of implementation also makes phishing attacks impossible since a passkey can only be used on the website on which registration happened. Your users will no longer be tricked into signing in to an attacker's website. &lt;/p&gt;

&lt;p&gt;Using passkeys instead of password-based authentication can lead to faster and more secure sign-ins, reduce the cost of multi-factor authentication such as SMS or e-mail, and provide a faster sign-up process. Passkeys can also be used to safely access your account on a friend's device or in a public library, thanks to passkey QR codes. This gives convenience that other sign-in methods, such as MagicURL or OAuth2, do not have.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm0nlz8q695agejo4980h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm0nlz8q695agejo4980h.png" alt="Touch ID modal"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step by step implementation
&lt;/h2&gt;

&lt;p&gt;Implementing passkey involves multiple steps to keep the entire process secure and reliable. The following technologies will be used in this demo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://simplewebauthn.dev/" rel="noopener noreferrer"&gt;SimpleWebAuthn&lt;/a&gt; for passkey implementation&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://alpinejs.dev/" rel="noopener noreferrer"&gt;Alpine.js&lt;/a&gt; for reactive frontend&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://nodejs.org/en" rel="noopener noreferrer"&gt;Node.js&lt;/a&gt; for custom backend endpoints&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://appwrite.io/" rel="noopener noreferrer"&gt;Appwrite&lt;/a&gt; for user management, databases, and serverless functions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fizoyetoeu79ycolc57n5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fizoyetoeu79ycolc57n5.png" alt="Demo application showcase"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The entire source code can be found on &lt;a href="https://github.com/Meldiron/authenticate-with-passkey" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;. I recommend checking it out if you are planning on implementing passkey in your project. Code snippets in the article are simplified to showcase the basics of the implementation, while source code on GitHub covers all edge cases. Such edge cases are, for example, input validation, challenge cleanup, or improved logging.&lt;/p&gt;

&lt;h3&gt;
  
  
  Registration with Passkey
&lt;/h3&gt;

&lt;p&gt;The registration form consists of two very simple components, an e-mail and a submit button.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;

&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;x-data=&lt;/span&gt;&lt;span class="s"&gt;"app"&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;submit.prevent=&lt;/span&gt;&lt;span class="s"&gt;"onSignUp()&amp;gt;
  &amp;lt;input x-model="&lt;/span&gt;&lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;required=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Sign up&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Once the form is submitted, a client sends a request to start a registration challenge for a passkey.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;responseStart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/v1/challenges&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;responseStart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;With a response from the server, a client can request a browser to register a new passkey. What happens next depends on the browser and operating system, and multiple modals can be shown to the user for them to decide which passkey manager they want to use.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;registration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;SimpleWebAuthnBrowser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startRegistration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Earlier, a client sent a request to the &lt;code&gt;/v1/challenges&lt;/code&gt; endpoint. This endpoint needs to generate a new challenge, store it in a database, and return challenge details back to the client.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;appwrite&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prepareUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;SimpleWebAuthnServer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generateRegistrationOptions&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;rpName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Passkeys Demo (Appwrite)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;rpID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ALLOWED_HOSTNAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;userID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;userName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;userDisplayName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;attestationType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;none&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;authenticatorSelection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;residentKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;preferred&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;userVerification&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;preferred&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;authenticatorAttachment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;platform&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;challenge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;appwrite&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createChallenge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;challenge&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;challengeId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;challenge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;With that, the client can successfully generate a new passkey. Once the passkey is generated, the client needs to inform the server with a public key so it can be stored on the backend for future authentication.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;responseFinish&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/v1/challenges&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;PUT&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;challengeId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;challengeId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;registration&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Backend implements a new method on &lt;code&gt;/v1/challenges&lt;/code&gt; to verify that public key corresponds to the original challenge, and stores credentials in the database.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;challengeId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;registration&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;challenge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;appwrite&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getChallenge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;challengeId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;verification&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;SimpleWebAuthnServer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verifyRegistrationResponse&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;registration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;expectedChallenge&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;challenge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;expectedOrigin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ALLOWED_HOSTNAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;expectedRPID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ALLOWED_HOSTNAME&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;verified&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;registrationInfo&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;verification&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;verified&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;appwrite&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createCredentials&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;challenge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;credentialID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SimpleWebAuthnServerHelpers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isoUint8Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toHex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;registrationInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;credentialID&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;credentialPublicKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SimpleWebAuthnServerHelpers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isoUint8Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toHex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;registrationInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;credentialPublicKey&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;registrationInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;credentialDeviceType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;registrationInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;credentialDeviceType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;credentialBackedUp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;registrationInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;credentialBackedUp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;transports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;registration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transports&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;This code snippet took a long time to figure out, as Unit8Array cannot be easily stored in a database, so it needs to be encoded to a hex value before being stored. Later, it can be decoded before authentication verification. Attempting to JSON stringify and parse Unit8Array managed to store &lt;em&gt;some&lt;/em&gt; data, but didn't with properly.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;With all of that in place, the registration flow using passkey is finished.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fierj3wihlg46w3x3ywhb.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fierj3wihlg46w3x3ywhb.gif" alt="Sign in demo video"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Login with Passkey
&lt;/h3&gt;

&lt;p&gt;Similar to the sign-up process, it starts with a simple HTML form.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;

&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;x-data=&lt;/span&gt;&lt;span class="s"&gt;"app"&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;submit.prevent=&lt;/span&gt;&lt;span class="s"&gt;"onSignIn()&amp;gt;
  &amp;lt;input x-model="&lt;/span&gt;&lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;required=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Sign in&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Sign-in process also starts with a challenge. It's for the same public key cryptography secure reasons, but this time, the backend also takes advantage of having the public key in the database. When creating a challenge for the client, the server will also provide basic details about the allowed public key so the client device can suggest only relevant passkey.&lt;/p&gt;

&lt;p&gt;First, a client sends a request to a new &lt;code&gt;/v1/tokens&lt;/code&gt; endpoint, and starts the authentication process with challenge details from the server response:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;responseStart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/v1/tokens&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;responseStart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;authentication&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;SimpleWebAuthnBrowser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startAuthentication&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Next, a challenge is created on the server. This time, it's not a registration challenge, but instead, an authentication challenge.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;appwrite&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prepareUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;credential&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;appwrite&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getCredential&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;authenticator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;credential&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;SimpleWebAuthnServer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generateAuthenticationOptions&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;rpID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ALLOWED_HOSTNAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;userVerification&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;preferred&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;allowCredentials&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SimpleWebAuthnServerHelpers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isoUint8Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromHex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;authenticator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;credentialID&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;public-key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;transports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;authenticator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transports&lt;/span&gt;
  &lt;span class="p"&gt;}]&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;challenge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;appwrite&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createChallenge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;challenge&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;challengeId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;challenge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;options&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;With that implemented, the client is now prompted to select and authorize a passkey during the sign-in process. To finalize the authentication flow, the client sends one more request to the server with details about the selected passkey for final verification. Notice the server now responds with session details, which the client uses to authenticate into the Appwrite account.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;responseFinish&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/v1/tokens&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;PUT&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;challengeId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;challengeId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;authentication&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;responseFinish&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Last but not least, we implement final authentication verification on the server and implement logic to generate an Appwrite session for the client.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;challengeId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;authentication&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;challenge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;appwrite&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getChallenge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;challengeId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;credential&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;appwrite&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getCredential&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;challenge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;authenticator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;credential&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;authenticator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;credentialID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;SimpleWebAuthnServerHelpers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isoUint8Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromHex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;authenticator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;credentialID&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;authenticator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;credentialPublicKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;SimpleWebAuthnServerHelpers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isoUint8Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromHex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;authenticator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;credentialPublicKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;verification&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;SimpleWebAuthnServer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verifyAuthenticationResponse&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;authentication&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;expectedChallenge&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;challenge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;expectedOrigin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ALLOWED_HOSTNAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;expectedRPID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ALLOWED_HOSTNAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;authenticator&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;verified&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;verification&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;verified&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Incorrect passkey.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;corsHeaders&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;appwrite&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createSessionToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;challenge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;challenge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Authentication flow is now finished and users can sign into the application using passkey.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2vn7vb026lh2x6lwswbk.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2vn7vb026lh2x6lwswbk.gif" alt="Sign-in demo"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As mentioned earlier, code snippets in the article are simplified. Please refer to the source code on &lt;a href="https://github.com/Meldiron/authenticate-with-passkey" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; for a detailed implementation of the passkey authentication flow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Additional resources
&lt;/h2&gt;

&lt;p&gt;When first learning about passkey, &lt;a href="https://www.passkeys.com/guides" rel="noopener noreferrer"&gt;passkeys.com&lt;/a&gt; can be a great starting point to understand the basics.&lt;/p&gt;

&lt;p&gt;For implementing passkey on a website and in a Node.js server, &lt;a href="https://simplewebauthn.dev/docs/" rel="noopener noreferrer"&gt;SimpleWebAuthn docs&lt;/a&gt; has great descriptive examples.&lt;/p&gt;

&lt;p&gt;If you prefer to have basics provided and plan to customize the flow yourself, I would recommend using the &lt;a href="https://github.com/Meldiron/authenticate-with-passkey" rel="noopener noreferrer"&gt;Appwrite Function template&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you are looking for a Cloud solution that is easy to get up and running, you can use providers such as &lt;a href="https://www.hanko.io/" rel="noopener noreferrer"&gt;Hanko&lt;/a&gt; or &lt;a href="https://www.passkeys.io/" rel="noopener noreferrer"&gt;Passkeys.io&lt;/a&gt; or &lt;a href="https://auth0.com/docs/authenticate/database-connections/passkeys" rel="noopener noreferrer"&gt;Auth0&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>security</category>
      <category>tutorial</category>
      <category>javascript</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Serverless your way: Unleashing Appwrite Function’s true potential</title>
      <dc:creator>Matej Bačo</dc:creator>
      <pubDate>Thu, 07 Sep 2023 10:20:08 +0000</pubDate>
      <link>https://dev.to/appwrite/serverless-your-way-unleashing-appwrite-functions-true-potential-2l4f</link>
      <guid>https://dev.to/appwrite/serverless-your-way-unleashing-appwrite-functions-true-potential-2l4f</guid>
      <description>&lt;p&gt;After countless months of research and never-ending feedback from our community, we are excited to announce that Appwrite Function received the biggest upgrade ever! Appwrite 1.4 introduces new Appwrite Functions that are truly unique and innovative, with a combination of features not found anywhere else. We achieved this by listening to real problems that developers are facing.&lt;/p&gt;

&lt;p&gt;What problems, you might ask? One of the most common problems was using Functions as an endpoint, which was not really possible before. This was causing problems for incoming webhooks communication which is a common way to handle payments. Another regular issue was the customization of the build step, as private dependencies or TypeScript builds were not possible at all. Lastly, there were concerns about the speed of setting up a function and its environment variables.&lt;/p&gt;

&lt;p&gt;Now let’s see how we overcame those barriers, and what new possibilities were unlocked.&lt;/p&gt;

&lt;h2&gt;
  
  
  About Appwrite functions
&lt;/h2&gt;

&lt;p&gt;Appwrite Functions lets you extend Appwrite's capabilities by writing your own code that's deployed and managed by Appwrite. This means you can write your own custom backend solutions in Appwrite, without managing infrastructure. Build everything from custom REST endpoints, business logic, permission checks, or entirely new services on top of Appwrite. All with the ability to deploy from your GitHub repository, Appwrite Console, or through a custom CI/CD pipeline via our CLI.&lt;/p&gt;

&lt;p&gt;We truly believe that every application is unique in its own way, and Appwrite Functions are a way to express that. We put extra effort into making sure Functions can do anything, but there is always room for improvement. Since Appwrite 1.3, we noticed a lot of community projects such as &lt;a href="https://github.com/BoolCode/appwrite-funcover"&gt;Appwrite Funcover&lt;/a&gt; or Appwrite &lt;a href="https://github.com/ssa-web-solutions/appwrite-functions-proxy"&gt;Functions Proxy&lt;/a&gt;, and many GitHub feature requests like &lt;a href="https://github.com/appwrite/appwrite/issues/3530"&gt;Global Environment Variables&lt;/a&gt;, &lt;a href="https://github.com/appwrite/appwrite/issues/4054"&gt;Private Package in Functions&lt;/a&gt;, or &lt;a href="https://github.com/appwrite/appwrite/issues/5832"&gt;Function with Typescript&lt;/a&gt;. All of that was a foundation for what we introduced in Appwrite 1.4.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automatic deployments with GitHub
&lt;/h2&gt;

&lt;p&gt;With any serverless functions, it’s important to simplify the deployment process. If you don’t, chances are that developers will prefer the good old way of managing a small Linux server. In Appwrite, we have support for manual upload in the Console, or using a terminal with a single command. We also have 0 downtime deployment support, to allow production apps to also take advantage of Functions.&lt;/p&gt;

&lt;p&gt;With all of those cool features in place, we wanted to focus on empowering deployments. It becomes a nightmare for solo developers to manage a growing list of small functions that you have to redeploy, not to mention remembering which functions need to be deployed with specific changes you made. Our community also reported some unnecessary complexity when working on a Function in a team. The preferred way of deployment is using CLI, but there isn’t enough protection around who is allowed to deploy to production. That meant a longer onboarding process for teams to make sure developers were fully aware of how scary each command could be.&lt;/p&gt;

&lt;p&gt;In Appwrite 1.4, we introduced a fully automated deployment using GitHub. You can now connect your GitHub repository to an Appwrite Function, and each change you push will trigger a deployment. You also get comments on your commits and pull requests, to inform you about the status of deployment. By configuring the production branch, you can decide which changes go directly into production. GitHub deployments also integrate well with your CI/CD, as Appwrite places checks on pull requests, to allow you to block merging changes that failed to build in Appwrite.&lt;/p&gt;

&lt;p&gt;With GitHub deployment, your team is now protected from silly mistakes, and deployment is made even simpler. Alongside GitHub integration, we also added Function templates that let you get started with Functions within a few clicks! &lt;/p&gt;

&lt;h2&gt;
  
  
  HTTP functions and webhooks
&lt;/h2&gt;

&lt;p&gt;Our mission has always been to meet developers exactly where they are in their comfort zone. By building Appwrite on top of your existing knowledge we ensure you can learn quickly and build fast.&lt;/p&gt;

&lt;p&gt;When taking a deeper look into Functions, we noticed it wasn’t following those values. Some great examples are terms like &lt;code&gt;Payload&lt;/code&gt;, &lt;code&gt;Request variables&lt;/code&gt;, or &lt;code&gt;Custom Data&lt;/code&gt;. They are easy to understand, but if we used terms like &lt;code&gt;Request Body&lt;/code&gt; and &lt;code&gt;Environment Variables&lt;/code&gt;, there would be nothing you would need to understand. Those would be common programming terms most of us already know, and there are numerous resources about them already written.&lt;/p&gt;

&lt;p&gt;We also noticed Appwrite Functions had some limitations, as our community couldn’t find an easy way to integrate incoming webhooks with Appwrite. Incoming webhooks are a very common approach to integrating external services such as payment processing or chatbots. Due to that, our community made some tools such as &lt;a href="https://github.com/BoolCode/appwrite-funcover"&gt;Appwrite Funcover&lt;/a&gt; to overcome this problem.&lt;/p&gt;

&lt;p&gt;Let me say a huge &lt;strong&gt;thank you&lt;/strong&gt; to our community, which gave us the inspiration to redesign Appwrite Functions to follow HTTP standards.&lt;/p&gt;

&lt;p&gt;In Appwrite 1.4, we assign unique domain to every function. You can use that, or add your own custom domain. Visiting this domain is yet another way of executing Appwrite Functions. Thanks to this small change, we are now able to provide much more information in the execution context such as request method, path, headers, or body. We can now also support many new response types such as image, PDF file, redirect, or rendered HTML. You could say that Appwrite 1.4 now lets you build your own mini web server as Appwrite Function, allowing you to do anything you could with a custom backend.&lt;/p&gt;

&lt;p&gt;By following HTTP standards it’s now possible to handle payment processing, generate PDF invoices, run Discord or Slack bot, and even more. It’s now also possible to write a small HTTP framework inside the Appwrite Function and define your very own standalone service.&lt;/p&gt;

&lt;h2&gt;
  
  
  Global environment variables
&lt;/h2&gt;

&lt;p&gt;With every Appwrite release, we introduce new services or improve existing ones. While it’s important to ship fast, we also focus on ensuring services work well together.&lt;/p&gt;

&lt;p&gt;One of the most requested features since Appwrite 1.3 was an improvement to managing function environment variables. One common use-case for Appwrite Functions is to use Appwrite Server SDK and do administrative actions to documents, teams, or files. Since Appwrite doesn’t automatically provide an API key to every function (for security reasons), developers need to set it as an environment variable. This can get annoying very quickly, if you have to copy the same variables across dozens of functions you develop, not to mention there isn’t any easy way to do that either.&lt;/p&gt;

&lt;p&gt;In the latest release, we overcame all of those problems with a few small features. Firstly, you can now set environment variables in project settings. Variables set here are provided for all the functions inside your project. No matter how many functions you have, you can now set the API key variable in one place, and get it distributed across all of them. We understand that customization of those might be necessary, which is why we decided to add automatic conflict resolution. If there is a variable defined in both project and function settings, the value from function settings takes precedence.&lt;/p&gt;

&lt;p&gt;Secondly, in the Console, we designed a simple Variables Editor to let you edit all variables in a single action. This editor lets you import or export variables in ENV or JSON format, delete all variables, create them, copy, edit, read… All of which can be done in one text area without having to switch between modals.&lt;/p&gt;

&lt;h2&gt;
  
  
  Flexible build step
&lt;/h2&gt;

&lt;p&gt;Customization is a must-have for any backend as a service. In the previous version, we wanted to find a good balance between customizability and simplicity. We tried to automatically detect what build step your function needs, but this check was simple and didn't account for custom build steps. We have seen our community face issues with TypeScript functions in Node, or issues with private dependencies in Python. We also noticed some CI/CD capabilities were missing for compiled languages, such as asset minification.&lt;/p&gt;

&lt;p&gt;With Appwrite 1.4, we don't do any guessing anymore and rely on what you configure when creating a function or deployment. We still suggest reasonable defaults, but it's up to you to decide if that makes sense for your function, or not. As mentioned above, this allows custom commands such as &lt;code&gt;tsc&lt;/code&gt; for TypeScript builds, private dependencies with token authorization &lt;code&gt;export NPM_TOKEN="..."; npm install&lt;/code&gt;, or custom CI/CD tasks &lt;code&gt;sh custom-build.sh&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You can now also keep a database or storage setup as part of your Appwrite Function. You can configure the build command to be &lt;code&gt;npm install &amp;amp;&amp;amp; node setup.js&lt;/code&gt;, and have the script to set up Appwrite Database and collection. You can see an example of that in our &lt;a href="https://github.com/appwrite/templates/blob/main/node/url-shortener/src/setup.js"&gt;URL Shorter Template&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;With the introduction of custom build commands, we decided to upgrade the build step even further and introduce real-time logs. You can now use build commands to minify, optimize, cache, test, and a lot more. Considering your build can now take minutes, we decided to improve the Console to show logs as they happen in real-time. This gives you much more info than the previous &lt;code&gt;Building&lt;/code&gt; state with you sitting tight and hoping all goes well.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing notes
&lt;/h2&gt;

&lt;p&gt;Appwrite Functions got the biggest upgrade in Appwrite 1.4, and are now capable of doing so much more.&lt;/p&gt;

&lt;p&gt;We would love to hear your feedback! We invite you to join our &lt;a href="https://appwrite.io/discord"&gt;Discord Server&lt;/a&gt; and share what you think about the new Appwrite Functions and 1.4 release in general. We are also here to chat, give support, and share memes.&lt;/p&gt;

&lt;p&gt;Stay tuned for more information about Functions, as we will be publishing more in-depth technical article about how we built this technology, what challenges we faced, and how we managed to resolve them.&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>opensource</category>
      <category>programming</category>
      <category>git</category>
    </item>
    <item>
      <title>Join Celebrations! Appwrite 1.3 Ships Relationships</title>
      <dc:creator>Matej Bačo</dc:creator>
      <pubDate>Wed, 12 Apr 2023 09:33:48 +0000</pubDate>
      <link>https://dev.to/appwrite/join-celebrations-appwrite-13-ships-relationships-57fc</link>
      <guid>https://dev.to/appwrite/join-celebrations-appwrite-13-ships-relationships-57fc</guid>
      <description>&lt;p&gt;Our latest release Appwrite 1.3 includes the most requested feature, &lt;strong&gt;Database Relationships&lt;/strong&gt; 🎉 While relationships deserve all the spotlight, we are bringing many more exciting features. Databases were the focus of this release and got some new and shiny &lt;strong&gt;query operators&lt;/strong&gt;, such as &lt;code&gt;Query.select()&lt;/code&gt; or &lt;code&gt;Query.isNull()&lt;/code&gt;. Databases now allow give you more freedom with some of the &lt;strong&gt;index and page limits&lt;/strong&gt; eliminated. Oh, and I can’t stress this enough, the new Console UI with &lt;strong&gt;configurable layout&lt;/strong&gt; options and a &lt;strong&gt;multi-level menu&lt;/strong&gt; is just beautiful 😍 Auth service got some thrilling new &lt;strong&gt;password policy&lt;/strong&gt; configurations and Teams API now lets you set &lt;strong&gt;team preferences&lt;/strong&gt;. Functions service got improvements to &lt;strong&gt;variables UI&lt;/strong&gt; in Appwrite Console, and that’s not even everything! Grab your favorite snack and enjoy our announcement. 🍿&lt;/p&gt;

&lt;h2&gt;
  
  
  🤔 New to Appwrite?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://appwrite.io/"&gt;Appwrite&lt;/a&gt; is an open-source back-end-as-a-service that abstracts all the complexity of building a modern application by providing you with a set of REST, GraphQL, and Realtime APIs for your core back-end needs. Appwrite takes the heavy lifting for developers and handles user authentication and authorization, databases, file storage, cloud functions, webhooks, and much more!&lt;/p&gt;

&lt;h2&gt;
  
  
  🔗 Relationships in Database Service
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://appwrite.io/docs/databases"&gt;Appwrite Databases&lt;/a&gt; lets you store your data and share it with all of your app users. Relationships allow you to create connections between your data to make them predictable, consistent, and easy to work with. With Relationships Beta added to Appwrite, you can now set up the four most common types of relationships directly in your Appwrite Console, and start querying your data together.&lt;/p&gt;

&lt;p&gt;With one-to-one and many-to-one relations, you can now store similar data in separate collections to have separate permission rules. This lets you build secure apps but still keep reading data exceptionally simple.&lt;/p&gt;

&lt;p&gt;One-to-many relations bring a powerful replacement to all of your array attributes. With a related collection, you can now store data of any complexity, and query it as you like. You can also get all the data in one request without any need to do mapping on the client side.&lt;/p&gt;

&lt;p&gt;Many-to-many relationships are now much simpler. You no longer need to manage your own junction table as we do it for you. &lt;/p&gt;

&lt;p&gt;As complex as relationships sound, with Appwrite, there is no need to write lengthy queries. After setting up a relationship, simply read your documents and get exactly what you expect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;databases&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Databases&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;databases&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listDocuments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;production&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;calendars&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"total"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"documents"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Meetings"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"events"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Lunch Break"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2023-04-03T16:30:00.000+00:00"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Onboarding"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2023-04-03T18:00:00.000+00:00"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It’s that easy! 🤩 If the response payload ever grows too big,  with the new &lt;code&gt;Query.select()&lt;/code&gt; operator you can ask for only attributes you are interested in. More on that in a later section 🤫&lt;/p&gt;

&lt;h2&gt;
  
  
  🤸 Databases Getting Flexier
&lt;/h2&gt;

&lt;p&gt;Maximum page size and indexes make end-product faster but can create a lot of road bumps during development. These limits even created a blocker for some use cases. &lt;strong&gt;Not anymore!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The maximum page size for getting your documents was capped at 100. In Appwrite 1.3, this limit was eliminated. We noticed it felt limiting to many projects and implementing infinite cursor pagination wasn’t a cup of tea for everyone. While it is not recommended to query millions of documents in a single request, we made sure to provide you with tools, and yet you, a developer, make the decision.&lt;/p&gt;

&lt;p&gt;Strict indexes for queries in Appwrite 1.3 are no longer necessary! If you ever used Appwrite Databases, chances are, you have seen the error &lt;code&gt;⚠️Index Not Found&lt;/code&gt;. Such strict indexes can get in the way during development and can be a blocker to some use cases with complex filtering possibilities. With Appwrite 1.3, you are no longer going to get errors telling you to set up an index.&lt;/p&gt;

&lt;p&gt;Let’s get a little crazy and see what we can do now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;databases&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Databases&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;databases&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listDocuments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;production&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;events&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;999999&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nx"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;orderDesc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nx"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;orderAsc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nx"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;orderDesc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;date&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nx"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;greaterThan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;duration&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nx"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;description&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;urgent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As complex as this looks, Appwrite will accept this query and give you the results. As your app grows and this query gets slower, make sure to add indexes!😅&lt;/p&gt;

&lt;h2&gt;
  
  
  🔍 New Database Queries
&lt;/h2&gt;

&lt;p&gt;Since we released the last database refactor in Appwrite 1.0, we noticed a huge increase in developers using Appwrite Databases. Databases became much quicker, more powerful, and more stable. With all the new feedback from our community, we noticed queries need some more attention, which is exactly what we did 😎&lt;/p&gt;

&lt;p&gt;We are proud to announce the most requested query &lt;code&gt;Query.isNull()&lt;/code&gt; alongside &lt;code&gt;Query.isNotNull()&lt;/code&gt;. Null query lets you check if the value of an attribute in the document was provided, or was left empty during the creation of the document.&lt;/p&gt;

&lt;p&gt;With relationships added to databases, we decided to also introduce &lt;code&gt;Query.select()&lt;/code&gt; to allow developers to specify what attributes they are interested in, similarly to how it can be done with GraphQL. Specifying attributes with a select query not only decreases the size of the response, but also makes underlying queries faster and more efficient.&lt;/p&gt;

&lt;p&gt;We also added &lt;code&gt;Query.startsWith()&lt;/code&gt; and &lt;code&gt;Query.endsWith()&lt;/code&gt; to bring some more searching possibilities to your string attributes. While this doesn’t sound like much, it is a great addition for many applications as workarounds for this feature were overcomplicated.&lt;/p&gt;

&lt;p&gt;Last but not least, we added a &lt;code&gt;Query.between()&lt;/code&gt; to improve the performance of your numeric and datetime queries when you are looking for a specific range. This was already possible with &lt;code&gt;Query.greaterThan()&lt;/code&gt; and &lt;code&gt;Query.lessThan()&lt;/code&gt;, but by using &lt;code&gt;Query.between()&lt;/code&gt; we have seen improvements in performance. We are constantly working on adding new query capabilities and plan to keep introducing new ones in the future with each release.&lt;/p&gt;

&lt;p&gt;Let’s bring all the queries together and see them in action!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;databases&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Databases&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;databases&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listDocuments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;production&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;profiles&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isNotNull&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;employerId&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nx"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;phoneNumber&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;+61&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nx"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;between&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;age&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nx"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;twitterUrl&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;linkedInUrl&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We just filtered profiles to only see unemployed folks with a phone number from Canada, and a good age for student part-time job. We are only fetching their socials, so we can easily get in touch with them.&lt;/p&gt;

&lt;h2&gt;
  
  
  ✨ Improved Passwords Security
&lt;/h2&gt;

&lt;p&gt;To keep users of your application safe, Appwrite already had a minimum of 8 character limit for the password. Today we have improved password security with two new rules. 🔥&lt;/p&gt;

&lt;p&gt;With Appwrite 1.3 you can now enable password history and configure its length. With password history enabled, your users won’t be allowed to use the exact same password that they used previously when changing their password. The length of password history lets you set how much into the past should Appwrite remember, a maximum of up to 20 passwords.&lt;/p&gt;

&lt;p&gt;You can now also enable a rule for password dictionary. Appwrite knows what are the most common passwords, and with this rule enabled, it will not allow you users to set any of those passwords. It prevents your users from having passwords like &lt;code&gt;password&lt;/code&gt;, &lt;code&gt;123456678&lt;/code&gt;, or &lt;code&gt;qwertyui&lt;/code&gt;. Appwrite currently knows the 10,000 most commonly used passwords thanks to the same list used by other industry-leading auth providers. You can check out the &lt;a href="https://github.com/danielmiessler/SecLists/blob/master/Passwords/Common-Credentials/10k-most-common.txt"&gt;dictionary list&lt;/a&gt; on GitHub.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4tdc5h66hqw3179td9wv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4tdc5h66hqw3179td9wv.png" alt="Password Policies Console UI" width="800" height="479"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  🧑‍🤝‍🧑 Teams Get Preferences
&lt;/h2&gt;

&lt;p&gt;Teams API got an upgrade and can now store preferences on teams. Similarly to user preferences, this feature is intended to help you store app configuration and apply it in a proper scope. You can, for example, use team preferences for business apps where the team owner (IT department in a company) does all the configuration and layout setup. Then all team members (all employees) read this setup from preferences and see the app in exactly the same way.&lt;/p&gt;

&lt;p&gt;With this addition to the preferences family, you can now store preferences on all necessary levels. Let’s consider a scenario when we are storing if a dark theme is enabled or not. By writing to a local device, you are creating a preference on the session level. When you open the app on a new device, you will no longer have the dark theme. By using user preferences and storing them on the user level, the dark theme would be enabled as soon as you sign into your account. Finally, with team-level preferences, you can share the theme with multiple users such as students, employees, friends, or the community. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5ue8dblkilimztsho9jz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5ue8dblkilimztsho9jz.png" alt="Team Preferences Console UI" width="800" height="303"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  🔑 Importing Function Variables
&lt;/h2&gt;

&lt;p&gt;Variables in Appwrite Functions lets you set secrets that you can securely access in your code. With Appwrite 1.3 we added a highly-requested button to &lt;code&gt;Import .env file&lt;/code&gt; 🤯 With this new feature you can drag&amp;amp;drop the &lt;code&gt;.env&lt;/code&gt; file from your function folder and Console will take care of adding all variables defined in there. Additionally, all existing variables get updated with values from your file. This is exciting for those of us who have many variables and don’t want to create them one by one. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flgd3d04algqcqyczg67d.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flgd3d04algqcqyczg67d.gif" alt="Function Variables Import GIF" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  ⏩ What’s Next?
&lt;/h2&gt;

&lt;p&gt;With Appwrite 1.3 out in the public, we are starting to actively work on features for Appwrite 1.4. Some of you may have already noticed GitHub activity around Functions, which we plan to be our focus for the next release. As always, we are looking forward to hearing feedback from you and prioritizing features &lt;strong&gt;YOU&lt;/strong&gt; are anticipating.&lt;/p&gt;

&lt;h2&gt;
  
  
  📚 Learn More
&lt;/h2&gt;

&lt;p&gt;You can use the following resources to learn more and get help:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/appwrite/appwrite/"&gt;🚀 Appwrite GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://appwrite.io/docs"&gt;📜 Appwrite Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://appwrite.io/discord"&gt;💬 Discord Community&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>opensource</category>
      <category>programming</category>
      <category>news</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Go Limitless with New Appwrite Queries</title>
      <dc:creator>Matej Bačo</dc:creator>
      <pubDate>Wed, 14 Sep 2022 13:18:57 +0000</pubDate>
      <link>https://dev.to/appwrite/go-limitless-with-new-appwrite-queries-2ajg</link>
      <guid>https://dev.to/appwrite/go-limitless-with-new-appwrite-queries-2ajg</guid>
      <description>&lt;p&gt;&lt;a href="https://appwrite.io/" rel="noopener noreferrer"&gt;Appwrite&lt;/a&gt; is an open-source backend-as-a-service that abstracts all the complexity involved in building a modern application by providing you with a set of REST APIs for your core backend needs. Appwrite handles user authentication and authorization, real-time databases, cloud functions, webhooks, and much more!&lt;/p&gt;

&lt;p&gt;What’s even more amazing is that we recently became number 1! 🥳 Long-awaited 1.0 release brings amazing features to make web and app development even more &lt;strong&gt;FUN&lt;/strong&gt;. One of them being a new query syntax that brings numerous possibilities, but first, let’s understand what it means to &lt;code&gt;query&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  🔍 What Is a Query?
&lt;/h2&gt;

&lt;p&gt;The database is a critical part of any application. It’s a place where all the information generated by users is stored. Storing data is important but only useful if you can &lt;strong&gt;read the data&lt;/strong&gt;. In the end, who would use an app where they can create a profile but never look at it? 🤔&lt;/p&gt;

&lt;p&gt;It’s as simple as that! Querying is the process of reading the data of your application. Queries can be as simple as “Give me all you got”, but can also get robust with many conditions, sorting operations, or pagination.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx8mb9r3pzrpdo91lbpl7.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx8mb9r3pzrpdo91lbpl7.jpg" alt="Show me what you got meme"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s see a query in action! For this example, I will use SQL (Structured &lt;strong&gt;Query&lt;/strong&gt; Language), which lets us read data from a database by writing a human-like sentence.&lt;/p&gt;

&lt;p&gt;Let’s start with a simple query to get all JavaScript frameworks in the world:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;frameworks&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This query could serve all of our needs but would get extremely slow as our database starts to grow. Let’s improve the query by filtering the results and only get popular frameworks:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;frameworks&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;popularity&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;We might still be getting thousands of results, but we never show that on the website, right? Let’s only get the first 10 results:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;frameworks&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;popularity&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt; &lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Finally, let’s make sure to show the most popular frameworks first:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;frameworks&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;popularity&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt; &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;popularity&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt; &lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;We just built a basic query! 💪 Let’s now take this knowledge and build some queries that we can use to read data from Appwrite Database.&lt;/p&gt;

&lt;h2&gt;
  
  
  🖋️ Appwrite Query Syntax
&lt;/h2&gt;

&lt;p&gt;Let's start by taking a look at &lt;strong&gt;filter queries&lt;/strong&gt;. These queries are meant to reduce the amount of results by checking if a condition is met regarding each document. Appwrite supports many filter queries that let you compare against a word, text, number or booleans. Following is a table of all available filter queries and an example of how such a query could look like in SQL world to make understanding easier:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Appwrite Query&lt;/th&gt;
&lt;th&gt;SQL Query&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Query.equal("role", "Developer")&lt;/td&gt;
&lt;td&gt;WHERE role = 'Developer'&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Query.equal("role", [ "Developer", "Designer" ])&lt;/td&gt;
&lt;td&gt;WHERE role = 'Developer' OR role = 'Designer'&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Query.notEqual("category", "Flutter")&lt;/td&gt;
&lt;td&gt;WHERE category != "Flutter"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Query.lessThan("age", 100)&lt;/td&gt;
&lt;td&gt;WHERE age &amp;lt; 100&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Query.lessThanEqual("age", 100)&lt;/td&gt;
&lt;td&gt;WHERE age &amp;lt;= 100&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Query.greaterThan("balance", 4.99)&lt;/td&gt;
&lt;td&gt;WHERE balance &amp;gt; 4.99&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Query.greaterThanEqual("balance", 4.99)&lt;/td&gt;
&lt;td&gt;WHERE balance &amp;gt;= 4.99&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Query.search("content", "phone number")&lt;/td&gt;
&lt;td&gt;WHERE MATCH(content) AGAINST('phone number' IN BOOLEAN MODE)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Next you can take advantage of &lt;strong&gt;sort queries&lt;/strong&gt; that lets you order results of a query in a specific way. You can see this feature on all eshops that let you sort products by popularity, price or reviews. Following queries are available in Appwrite:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Appwrite Query&lt;/th&gt;
&lt;th&gt;SQL Query&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Query.orderAsc("price")&lt;/td&gt;
&lt;td&gt;ORDER BY price ASC&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Query.orderDesc("$createdAt")&lt;/td&gt;
&lt;td&gt;ORDER BY createdAt DESC&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Last but not least, you can write &lt;strong&gt;pagination queries&lt;/strong&gt;. There are different ways of paginating over your database that you can learn more in our &lt;a href="https://dev.to/appwrite/this-is-why-you-should-use-cursor-pagination-4nh5"&gt;Database Paginations&lt;/a&gt; article. In short, we could do offset pagination using limit and offset queries, or cursor pagination using limit and cursor. All of that is possible in Appwrite using following queries:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Appwrite Query&lt;/th&gt;
&lt;th&gt;SQL Query&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Query.limit(10)&lt;/td&gt;
&lt;td&gt;LIMIT 10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Query.offset(30)&lt;/td&gt;
&lt;td&gt;OFFSET 30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Query.cursorAfter("documentId15")&lt;/td&gt;
&lt;td&gt;WHERE id &amp;gt; 15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Query.cursorBefore("documentId50")&lt;/td&gt;
&lt;td&gt;WHERE id &amp;lt; 50&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  🧰 Querying Any Appwrite Service
&lt;/h2&gt;

&lt;p&gt;If you used Appwrite before, you might have noticed that all of the above features were already available, just in a different syntax. So… Why the change?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;✨ Consistency ✨&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;These features were only available in some services! 😦 Our mission was to implement queries into all list methods (where it makes sense), but that would be pain for you to learn, and pain for us to maintain. Thanks to Queries syntax Appwrite now offers a consistent interface to query &lt;strong&gt;any&lt;/strong&gt; resource in an exactly the same way.  Let’s see it in action!&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Databases&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Query&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;appwrite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;client&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setEndpoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://[HOSTNAME_OR_IP]/v1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setProject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;PROJECT_ID]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;database&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Databases&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Get products from eshop database that are published and cost below 50$. Ordered by price to get most expensive first on third page, getting 10 items per page&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;productsList&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listDocuments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;eshop&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;products&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;published&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nx"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lessThan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;price&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;49.99&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nx"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nx"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nx"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;orderDesc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;price&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="c1"&gt;// Get up to 50 disabled collections in eshop database&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;collectionsList&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listCollections&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;eshop&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;enabled&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nx"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="c1"&gt;// Get database by name&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;databasesList&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="nx"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;eshop&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nx"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Let’s see a few more examples with different services 😇&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;storage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Storage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Get all JPEG or PNG files ordered by file size&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;filesList&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listFiles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;productPhotos&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mimeType&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;image/jpeg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;image/png&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;]),&lt;/span&gt;
    &lt;span class="nx"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;orderAsc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sizeActual&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;functions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Functions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Get failed executions that were triggered by http request and lasted at least 5 seconds&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;executionsList&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;functions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listExecutions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;createOrder&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;status&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;failed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nx"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;trigger&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nx"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;greaterThanEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;time&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// in seconds&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;teams&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Teams&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Get 5th biggest team that has at least 100 members&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;teamsList&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;teams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="nx"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;greaterThan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;total&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nx"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;orderDesc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;total&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nx"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nx"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Users&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Find all Johns that verified their email address&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;usersList&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="nx"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;emailVerification&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nx"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;John&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;As you can see, possibilities are limitless! 🤯 What’s more, the new syntax opens doors for new exciting features such as join, specific selects, subqueries, and new operators. We will continue working and making the Appwrite database more flexible and closer to the under the hood database being used, whether it’s MySQL, MariaDB, or in the future, MongoDB.&lt;/p&gt;

&lt;h2&gt;
  
  
  👨‍🎓 Conclusion
&lt;/h2&gt;

&lt;p&gt;New Appwrite queries syntax introduced in 1.0 brings long-awaited consistency across all Appwrite services. This not only eases a learning curve of Appwrite, but also introduces new possibilities to allow you to create even more amazing applications!&lt;/p&gt;

&lt;h2&gt;
  
  
  📚 Learn more
&lt;/h2&gt;

&lt;p&gt;You can use the following resources to learn more and get help:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🚀 &lt;a href="https://github.com/appwrite" rel="noopener noreferrer"&gt;Appwrite Github&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📜 &lt;a href="https://appwrite.io/docs" rel="noopener noreferrer"&gt;Appwrite Docs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;💬 &lt;a href="https://appwrite.io/discord" rel="noopener noreferrer"&gt;Discord Community&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>opensource</category>
      <category>database</category>
      <category>programming</category>
    </item>
    <item>
      <title>Appwrite Loves Open Source: Why I Chose To Sponsor Offen</title>
      <dc:creator>Matej Bačo</dc:creator>
      <pubDate>Mon, 12 Sep 2022 13:56:03 +0000</pubDate>
      <link>https://dev.to/appwrite/appwrite-loves-open-source-why-i-chose-to-sponsor-offen-5efn</link>
      <guid>https://dev.to/appwrite/appwrite-loves-open-source-why-i-chose-to-sponsor-offen-5efn</guid>
      <description>&lt;p&gt;Open-source is at the ❤️ of everything we do at Appwrite, and we want to enable and foster the open-source community that helped us grow to thrilling 24,000 stars on &lt;a href="https://github.com/appwrite/appwrite"&gt;GitHub&lt;/a&gt;. Open-source projects require a great deal of effort to maintain and grow. We use open-source tools every day to build Appwrite, and we want to help our community. To give back, each Appwrite engineer gets to pick an open-source project for Appwrite to sponsor for one year.&lt;/p&gt;

&lt;h2&gt;
  
  
  👁️ Fair analytics tool
&lt;/h2&gt;

&lt;p&gt;Offen’s main product is &lt;a href="https://www.offen.dev/"&gt;web analytics&lt;/a&gt;, which is quite unique if you ask me. The analytics tool is a core requirement of any commercial website out there, as it provides many KPIs to define marketing success. Not only that, analytics can help you target your services to the right clients, increasing your overall income.&lt;/p&gt;

&lt;p&gt;There are many (even self-hosted) analytics tools, but Offen is unique enough to beat them all in my eyes. While others focused on collecting valuable information and storing them lawfully, Offen’s core value is to create visitor-friendly analytics. At your first interaction with Offen, you will notice it only allows first-party cookies, which is a great example of how they focus on visitor security in exchange for making setup a bit more complex. Once you set it up, you notice a second important feature, you can’t really see visitors in your dashboard yet. Offen is opt-in only, which means, you don’t track visitor until he clicks ‘Allow’. Offen also comes with a dashboard for visitors to see what &lt;strong&gt;exact&lt;/strong&gt; information you have about them, with an option to opt-out or delete all data with 1 click.&lt;/p&gt;

&lt;p&gt;Alongside these product-defining features, you can see functionality that can easily compare with industry-leading competitors:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Customizable consent banner&lt;/li&gt;
&lt;li&gt;Comply with GDPR&lt;/li&gt;
&lt;li&gt;End-to-end encryption&lt;/li&gt;
&lt;li&gt;Much more!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I am yet to use Offen analytics tool on a real project, but since day one I learned about it, I have been recommending it as a Plausible and Google Analytics alternative.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F90iao220m9eq9i2inrxu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F90iao220m9eq9i2inrxu.png" alt="Offen showcase" width="800" height="1328"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  🧰 Developer tooling
&lt;/h2&gt;

&lt;p&gt;Alongside the amazing analytics project Offen has to provide, their GitHub organization includes many different tools such as &lt;a href="https://github.com/offen/schemaify"&gt;schemaify&lt;/a&gt;, &lt;a href="https://github.com/offen/analyticstxt"&gt;analyticstxt&lt;/a&gt; or &lt;a href="https://github.com/offen/l10nify"&gt;l10nify&lt;/a&gt;. The one tool I like the most is &lt;a href="https://github.com/offen/docker-volume-backup"&gt;docker-volume-backup&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Nowadays many self-hosted projects provide Docker support to ensure simple setup, upgrade, deployment and maintenance. Docker also often solves the legendary quote ‘It works on my machine’. Once you start using Docker on production (which you can and should), you will notice there is no backup&amp;amp;restore mechanism available by default.&lt;/p&gt;

&lt;p&gt;Offen provides a Docker image that you can run alongside your Docker application to back up anything you might need. Under the hood, the Offen container mounts to the exact same volumes your application does, and backups files depending on your configuration.&lt;/p&gt;

&lt;p&gt;What’s cool is Offen’s Docker Volume Backup can be as simple or as complex as your project needs. On my server, I started with a simple &lt;code&gt;backup.sh&lt;/code&gt; script, but gradually improved it to a more mature solution with many features Offen has to provide:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Recurring backups (daily/weekly/monthly)&lt;/li&gt;
&lt;li&gt;Upload backups to the cloud&lt;/li&gt;
&lt;li&gt;Mirrored backups to multiple storage providers&lt;/li&gt;
&lt;li&gt;Maintenance mode during backup to ensure data integrity&lt;/li&gt;
&lt;li&gt;Alerts about failed backups&lt;/li&gt;
&lt;li&gt;Backup encryption&lt;/li&gt;
&lt;li&gt;Backup rotation to remove old ones&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With such advanced backup in place, all worries about data loss have been taken from my shoulder. I am so confident with Offen’s backup tool I have been recommending it to the Appwrite community to properly &lt;a href="https://gist.github.com/Meldiron/47b5851663668102a676aff43c6341f7"&gt;backup their Appwrite instance&lt;/a&gt;!&lt;/p&gt;

&lt;h2&gt;
  
  
  ☢️ Open-source Software (OSS) Is Hard
&lt;/h2&gt;

&lt;p&gt;Since Appwrite is open-source, we understand the challenges that OSS projects face. If you fall in love ❤️ with an open-source project (like we have), consider checking out ways to contribute. Most OSS projects happily accept contributions in their own way, whether they be in the form of commits, bug 🐛 reports, advocacy, or even monetary 💰 support. If you love &lt;code&gt;Offen&lt;/code&gt;, consider joining us as a sponsor. Or, if you're interested in contributing to Appwrite, check out our &lt;a href="https://github.com/appwrite/appwrite/blob/master/CONTRIBUTING.md"&gt;contribution guide&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  🔗 Learn more about Appwrite
&lt;/h2&gt;

&lt;p&gt;Check out Appwrite as the backend for your next &lt;a href="https://appwrite.io/docs/getting-started-for-web"&gt;Web&lt;/a&gt;, &lt;a href="https://appwrite.io/docs/getting-started-for-flutter"&gt;Flutter&lt;/a&gt;, or &lt;a href="https://appwrite.io/docs/getting-started-for-server"&gt;Server&lt;/a&gt; application. Here are some handy links for more information:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/appwrite/appwrite/blob/master/CONTRIBUTING.md"&gt;Appwrite Contribution Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://appwrite.io/discord"&gt;Appwrite Discord&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/appwrite"&gt;Appwrite Github&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://appwrite.io/docs"&gt;Appwrite Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>opensource</category>
      <category>security</category>
    </item>
    <item>
      <title>Appwrite Hand-In-Hand with Svelte Kit (SSR)</title>
      <dc:creator>Matej Bačo</dc:creator>
      <pubDate>Sun, 05 Jun 2022 14:10:30 +0000</pubDate>
      <link>https://dev.to/meldiron/appwrite-hand-in-hand-with-svelte-kit-ssr-5097</link>
      <guid>https://dev.to/meldiron/appwrite-hand-in-hand-with-svelte-kit-ssr-5097</guid>
      <description>&lt;p&gt;&lt;em&gt;If you are here only to see how to make SSR work, you can jump into ⚡ Server Side Rendering and 🚀 Deployment sections. They explain it all. You can also check out the &lt;a href="https://github.com/Meldiron/almost-casino" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Let's build a project!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In this article, we will build &lt;strong&gt;Almost Casino&lt;/strong&gt;, a web application that allows you to sign in and play coin flip with virtual currency called &lt;strong&gt;Almost Dollar&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;As you can see, not the brightest idea... The point of the project is not to build a 1B$ company in a weekend, instead, to showcase how we can achieve server-side rendering with &lt;a href="https://appwrite.io/" rel="noopener noreferrer"&gt;Appwrite&lt;/a&gt; backend.&lt;/p&gt;

&lt;h2&gt;
  
  
  🖼️ Pics, pics, pics!
&lt;/h2&gt;

&lt;p&gt;Let's see what we are going to build 😎&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhaowyag5pdlwgkdijiki.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhaowyag5pdlwgkdijiki.png" alt="Almost Casino showcase"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A single page that shows the ID of the currently logged-in user, his current balance, and a coin flip game. When playing coin flip, you can configure a bet and pick which side of the coin you would like to bet on. For the sick of simplicity, there will be no coin-flipping animation. Do you know what that means? 👀 &lt;strong&gt;YOU&lt;/strong&gt; can add the cool-looking animation!&lt;/p&gt;

&lt;h2&gt;
  
  
  📃 Requirements
&lt;/h2&gt;

&lt;p&gt;We will be using multiple technologies in this demo application, and having a basic understanding of them will be extremely helpful.&lt;/p&gt;

&lt;p&gt;Regarding the JavaScript framework, we will be using a simple and fun framework &lt;a href="https://svelte.dev/" rel="noopener noreferrer"&gt;Svelte&lt;/a&gt;. To allow routing and introduce better folder structure as well as the possibility of SSG and SSR, we are going to use &lt;a href="https://kit.svelte.dev/" rel="noopener noreferrer"&gt;Svelte Kit&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To give our application some cool styles, we will use &lt;a href="https://tailwindcss.com/" rel="noopener noreferrer"&gt;TailwindCSS&lt;/a&gt;, a CSS library for rapid designing.&lt;/p&gt;

&lt;p&gt;Instead of writing our own backend from scratch, we will be using backend-as-a-service &lt;a href="https://appwrite.io/" rel="noopener noreferrer"&gt;Appwrite&lt;/a&gt; to build and deploy server logic easily.&lt;/p&gt;

&lt;p&gt;Last but not least, we will use &lt;a href="https://vercel.com/" rel="noopener noreferrer"&gt;Vercel&lt;/a&gt; as our static.&lt;/p&gt;

&lt;h2&gt;
  
  
  🛠️ Create Svekte Kit Project
&lt;/h2&gt;

&lt;p&gt;The whole Almost Casino application is open-sourced and can be found in &lt;a href="https://github.com/Meldiron/almost-casino" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;. The app is also live on the &lt;a href="https://app.almost-casino.matejbaco.eu/" rel="noopener noreferrer"&gt;app.almost-casino.matejbaco.eu&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For those who follow along, let's create a new Svelte Kit project:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;npm init svelte almost-casino


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

✔ Which Svelte app template? › Skeleton project
✔ Add &lt;span class="nb"&gt;type &lt;/span&gt;checking? › TypeScript
✔ Add ESLint &lt;span class="k"&gt;for &lt;/span&gt;code linting? … No / Yes
✔ Add Prettier &lt;span class="k"&gt;for &lt;/span&gt;code formatting? … No / Yes
✔ Add Playwright &lt;span class="k"&gt;for &lt;/span&gt;browser testing? … No / Yes


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Next, let's enter our new project and run the development server:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;almost-casino
&lt;span class="nv"&gt;$ &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;npm run dev


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;We now have the Svelte Kit web application running and listening on &lt;code&gt;localhost:3000&lt;/code&gt; 🥳&lt;/p&gt;

&lt;p&gt;Let's make sure to follow &lt;a href="https://tailwindcss.com/docs/guides/sveltekit" rel="noopener noreferrer"&gt;Tailwind installation&lt;/a&gt; instructions to install it into our newly created Svelte Kit project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Appwrite backend setup
&lt;/h2&gt;

&lt;p&gt;If you don't have the Appwrite server running yet, please follow &lt;a href="https://appwrite.io/docs/installation" rel="noopener noreferrer"&gt;Appwrite installation&lt;/a&gt; instructions.&lt;/p&gt;

&lt;p&gt;Once the server is ready, let's create an account and a project with the custom ID &lt;code&gt;almostCasino&lt;/code&gt;. Next, we go into the &lt;code&gt;Database&lt;/code&gt; section and create a collection with ID &lt;code&gt;profiles&lt;/code&gt;. In the setting of this collection, we set &lt;code&gt;collection-level&lt;/code&gt; permission with read access to &lt;code&gt;role:member&lt;/code&gt;, and write access empty. Finally, we add the float attribute &lt;code&gt;balance&lt;/code&gt; and mark it as required.&lt;/p&gt;

&lt;h2&gt;
  
  
  🤖 Appwrite Service
&lt;/h2&gt;

&lt;p&gt;Let's start by creating a &lt;code&gt;.env&lt;/code&gt; file and putting information about our Appwrite project in there:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

VITE_APPWRITE_ENDPOINT=http://localhost/v1
VITE_APPWRITE_PROJECT_ID=almostCasino


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Before coding the application, let's create a class that will serve as an interface for all communication with our Appwrite backend. Here we will have methods for authentication, managing profile, and playing the coin flip game. Let's create new file &lt;code&gt;src/lib/appwrite.ts&lt;/code&gt; with all of the methods:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Appwrite&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;RealtimeResponseEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Models&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;appwrite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;appwrite&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Appwrite&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;appwrite&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setEndpoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;VITE_APPWRITE_ENDPOINT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setProject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;VITE_APPWRITE_PROJECT_ID&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Profile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;balance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;Models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Document&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AppwriteService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// SSR related&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="nf"&gt;setSSR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cookieStr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;authCookies&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
    &lt;span class="nx"&gt;authCookies&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;`a_session_&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;VITE_APPWRITE_PROJECT_ID&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cookieStr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;appwrite&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;X-Fallback-Cookies&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;authCookies&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Authentication-related&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;createAccount&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;appwrite&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createAnonymousSession&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;getAccount&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;appwrite&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;signOut&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;appwrite&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deleteSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;current&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Profile-related&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;getProfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Profile&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;appwrite&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;functions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createExecution&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;createProfile&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusCode&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;subscribeProfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RealtimeResponseEvent&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Profile&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;appwrite&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`collections.profiles.documents.&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Game-related&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;bet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;betPrice&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;betSide&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tails&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;heads&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;appwrite&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;functions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createExecution&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;placeBet&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="nx"&gt;betPrice&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;betSide&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusCode&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;didWin&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;With this in place, we have everything ready to have proper communication between our Svelte Kit application and our Appwrite backend 💪&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can notice we execute some functions in this class, for instance, &lt;code&gt;createProfile&lt;/code&gt; or &lt;code&gt;placeBet&lt;/code&gt;. We use Appwrite Functions for these actions to keep them secure and not allow clients to make direct changes to their money balance. You can find the source code of these functions in &lt;a href="https://github.com/Meldiron/almost-casino/tree/master/functions" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  🔐 Authentication Page
&lt;/h2&gt;

&lt;p&gt;We start by creating a button to create an account. Since we are going to be using anonymous accounts, we don't need to ask visitor for any information:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;

&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt;
  &lt;span class="na"&gt;on:click=&lt;/span&gt;&lt;span class="s"&gt;{onRegister}&lt;/span&gt;
  &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"flex items-center justify-center space-x-3 bg-brand-600 hover:bg-brand-500 text-white rounded-none px-10 py-3"&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    {#if registering}
      &lt;span class="nt"&gt;&amp;lt;span&amp;gt;&lt;/span&gt;...&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
    {/if}
    &lt;span class="nt"&gt;&amp;lt;span&amp;gt;&lt;/span&gt;Create Anonymous Account&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;All of this goes into &lt;code&gt;src/routes/index.svelte&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Next let's add method that runs when we click the button, as well as &lt;code&gt;registering&lt;/code&gt; variable indicating loading status:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;

&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"ts"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;registering&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;onRegister&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;registering&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;registering&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;AppwriteService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createAccount&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/casino&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;registering&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Finally, let's add a logic to redirect user to &lt;code&gt;/casino&lt;/code&gt; route if we can see he is already logged in. This will allow visitors getting to &lt;code&gt;/&lt;/code&gt; to be automatically redirected to casino if they are already logged in:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;

&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;context=&lt;/span&gt;&lt;span class="s"&gt;"module"&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"ts"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$app/env&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;goto&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$app/navigation&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Alert&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$lib/alert&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AppwriteService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Profile&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$lib/appwrite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Loading&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$lib/Loading.svelte&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;account&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;AppwriteService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAccount&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;profile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;AppwriteService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getProfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;302&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/casino&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_err&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;That concludes our authentication page 😅 If you are feeling advantageous, feel free to add some cool styles around it.&lt;/p&gt;

&lt;h2&gt;
  
  
  🎲 Game Page
&lt;/h2&gt;

&lt;p&gt;Let's make a file &lt;code&gt;src/routes/casino.svelte&lt;/code&gt; to register our new route. In this file, let's start by writing a logic of loading the data. In there let's load user's account information, as well as profile:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;

&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;context=&lt;/span&gt;&lt;span class="s"&gt;"module"&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"ts"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$app/env&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;goto&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$app/navigation&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Alert&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$lib/alert&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AppwriteService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Profile&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$lib/appwrite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Loading&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$lib/Loading.svelte&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;account&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;AppwriteService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAccount&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;profile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;AppwriteService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getProfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;profile&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;302&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;By getting a profile, it is automatically created if not present already. That will automatically give us a balance of 500 almost dollars.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;These information can now be received in a JavaScript variable:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;

&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"ts"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Models&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;RealtimeResponseEvent&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;appwrite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Data from module&lt;/span&gt;
    &lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Profile&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;You can ignore types import for now. They will come into play later. Just make sure to keep it there 😛&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Next, let's show our account information. For sick of simplicity, we will only show the ID of the account:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;

&lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"mb-8 text-lg text-brand-700"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  There we go, account created! Don't believe? This is your ID:
  &lt;span class="nt"&gt;&amp;lt;b&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"font-bold"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;{account?.$id}&lt;span class="nt"&gt;&amp;lt;/b&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Let's also prepare a button for user to sign out:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;

&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt;
  &lt;span class="na"&gt;on:click=&lt;/span&gt;&lt;span class="s"&gt;{onSignOut}&lt;/span&gt;
  &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"flex items-center justify-center space-x-3 border-brand-600 border-2 hover:border-brand-500 hover:text-brand-500 text-brand-600 rounded-none px-10 py-3"&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    {#if signingOut}
      &lt;span class="nt"&gt;&amp;lt;span&amp;gt;&lt;/span&gt;...&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
    {/if}
    &lt;span class="nt"&gt;&amp;lt;span&amp;gt;&lt;/span&gt;Sign Out&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Next let's show user's fake dollars balance:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;

&lt;span class="nt"&gt;&amp;lt;h2&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"text-2xl font-bold text-brand-900 mb-8 mt-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Balance&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"mb-3 text-lg text-brand-700"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Your current blalance is:&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"mb-8 text-3xl font-bold text-brand-900"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  {profile?.balance}
  &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"text-brand-900 opacity-25"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Almost Dollars&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Lastly, let's add section for betting:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;

&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt;
  &lt;span class="na"&gt;bind:value=&lt;/span&gt;&lt;span class="s"&gt;{bet}&lt;/span&gt;
  &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"bg-brand-50 p-4 border-2 border-brand-600 placeholder-brand-600 placeholder-opacity-50 text-brand-600"&lt;/span&gt;
  &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"number"&lt;/span&gt;
  &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"Enter amount to bet"&lt;/span&gt;
&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt;
  &lt;span class="na"&gt;on:click=&lt;/span&gt;&lt;span class="s"&gt;{onBet('heads')}&lt;/span&gt;
  &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"flex items-center justify-center space-x-3 border-2 border-brand-600 hover:border-brand-500 bg-brand-600 hover:bg-brand-500 text-white rounded-none px-10 py-3"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  {#if betting}
    &lt;span class="nt"&gt;&amp;lt;span&amp;gt;&lt;/span&gt;...&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
  {/if}
  &lt;span class="nt"&gt;&amp;lt;span&amp;gt;&lt;/span&gt;Heads!&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt;
  &lt;span class="na"&gt;on:click=&lt;/span&gt;&lt;span class="s"&gt;{onBet('tails')}&lt;/span&gt;
  &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"flex items-center justify-center space-x-3 border-2 border-brand-600 hover:border-brand-500 bg-brand-600 hover:bg-brand-500 text-white rounded-none px-10 py-3"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  {#if betting}
    &lt;span class="nt"&gt;&amp;lt;span&amp;gt;&lt;/span&gt;...&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
  {/if}
  &lt;span class="nt"&gt;&amp;lt;span&amp;gt;&lt;/span&gt;Tails!&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;With all of that, HTML part of our casino page is ready! Let's start adding the logic by first adding method for signing out:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;signingOut&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;onSignOut&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;signingOut&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;signingOut&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;AppwriteService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;signOut&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;signingOut&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Next let's add a logic for our betting section, to allow submitting our coin flip bet:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;bet&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;betting&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;onBet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;side&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;heads&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tails&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;profile&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;account&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;betting&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;betting&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;didWin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;AppwriteService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;side&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;didWin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`You won &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;bet&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; Almost Dollars.`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`You lost &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;bet&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; Almost Dollars.`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;err&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;betting&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Let's finish off by adding a real-time subscription to our profile. This will automatically update the balance displayed on the website as soon as it changes on the backend:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;profile&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;AppwriteService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribeProfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onProfileChange&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;onProfileChange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RealtimeResponseEvent&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Profile&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;profile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;We have just finished the casino page! And.. I think... 🤔&lt;/p&gt;

&lt;p&gt;That makes our Almost Casino complete!!! Let's now look at the main topic of this application, 🌟 &lt;strong&gt;SSR&lt;/strong&gt; 🌟.&lt;/p&gt;

&lt;h2&gt;
  
  
  ⚡ Server Side Rendering  &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Let's do the magic! 🧙&lt;/p&gt;

&lt;p&gt;We start by creating &lt;code&gt;src/hooks.ts&lt;/code&gt; file. In there, we intercept each request and get Appwrite authorization headers. We also return it in order to later retrieve it as part of our session object:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;cookie&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cookie&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cookieStr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cookie&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cookies&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cookie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cookieStr&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;authCookie&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cookies&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;`a_session_&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;VITE_APPWRITE_PROJECT_ID&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;_legacy`&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;authCookie&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;For this to work, make sure to install 'cookie' library with &lt;code&gt;npm install cookie&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Next, at the beginning of every &lt;code&gt;load()&lt;/code&gt; function (one in &lt;code&gt;src/routes/index.svelte&lt;/code&gt;, one in &lt;code&gt;src/routes/casino.svelte&lt;/code&gt;), let's add these two lines to extract our authentication headers from the session, and apply then to Appwrite SDK:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="c1"&gt;// Using SSR auth cookies (from hook.ts)&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;authCookie&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;AppwriteService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setSSR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;authCookie&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;With this in place, SSR will now work properly! 😇 You might not see it yet due to cookies following same-domain policy, but we will address that in the deployment section.&lt;/p&gt;

&lt;h2&gt;
  
  
  🚀 Deployment  &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Since we will deploy to &lt;a href="https://vercel.com/" rel="noopener noreferrer"&gt;Vercel&lt;/a&gt;, let's make sure we use the correct adapter. We start by installing the adapter:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

npm i @sveltejs/adapter-vercel


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Next, let's update our &lt;code&gt;svelte.config.js&lt;/code&gt; to use our new adapter:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;adapter&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@sveltejs/adapter-vercel&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// This was 'adapter-auto' previously, we just need to rename to 'adapter-vercel`&lt;/span&gt;

&lt;span class="c1"&gt;// ...&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="na"&gt;kit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;edge&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;external&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
      &lt;span class="na"&gt;split&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="s2"&gt;```

This makes our app ready for Vercel! 😎 Let's make a Git repository out of our project, sign in to Vercel and deploy the app. There is no need for any special configuration.

Once the application is deployed, make sure to put both Appwrite and Vercel applications on the same domain in order for SSR to work with authentication cookies.

If your application runs on the root of the domain, for instance, `&lt;/span&gt;&lt;span class="nx"&gt;mycasino&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;com&lt;/span&gt;&lt;span class="s2"&gt;`, then your Appwrite could be on any subdomain like `&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mycasino&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;com&lt;/span&gt;&lt;span class="s2"&gt;`.

If you plan to host your application on a subdomain, make sure to host it under the subdomain on which Appwrite runs. For instance, you could have Appwrite on domain `&lt;/span&gt;&lt;span class="nx"&gt;mycasino&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;myorg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;com&lt;/span&gt;&lt;span class="s2"&gt;` and Vercel application on domain `&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mycasino&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;myorg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;com&lt;/span&gt;&lt;span class="s2"&gt;`.

## 👨‍🎓 Conclusion

We successfully built a project that confirms Appwrite plays well with all technologies out there. I am really happy I got this article out as there were many requests from the Appwrite community regarding SSR, and now I have a place to point them to, even with code examples 😇

This demo application can also serve as an example for building the same SSR logic with other frameworks such as Angular, Vue, or React. They all follow a really similar structure regarding SSR, and it all comes down to extracting cookies from requests and setting them on Appwrite SDK.

## 🔗 Useful Links

If you are bookmarking this article, here are some highly valuable links for you to keep an eye on:

-  [Almost Casino: GitHub repository](https://github.com/Meldiron/almost-casino)
- [Almost Casino: Live Demo](https://app.almost-casino.matejbaco.eu/)
- [Appwrite: Homepage](https://appwrite.io/)
- [Appwrite: Discord server (for any support)](https://appwrite.io/discord)
- [Svelte Kit: Docs](https://kit.svelte.dev/)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>svelte</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>TMStats: Trackmania Tracker</title>
      <dc:creator>Matej Bačo</dc:creator>
      <pubDate>Tue, 03 May 2022 16:23:19 +0000</pubDate>
      <link>https://dev.to/meldiron/tmstats-trackmania-tracker-1k1a</link>
      <guid>https://dev.to/meldiron/tmstats-trackmania-tracker-1k1a</guid>
      <description>&lt;h3&gt;
  
  
  Overview of My Submission
&lt;/h3&gt;

&lt;p&gt;Live Version: &lt;a href="//tmstats.eu"&gt;www.tmstats.eu&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.trackmania.com/"&gt;Trackmania&lt;/a&gt; is a racing game loved by dozen of thousands of active players. The game is full of minute-long racing tracks where people compete against medal times, author time, or even against each other. It comes with 25 professionally made tracks every three months, alongside one community-made track every day. I like the game because it feels rewarding no matter your skill level 😌 While professional players try to have the best time on the map fighting for 0.001 second difference, you and your friends can try to get silver medals on all maps.&lt;/p&gt;

&lt;p&gt;Trackmania has numerous apps for players that make the game even more enjoyable, such as &lt;a href="https://trackmania.io/"&gt;Trackmania.io matchmaking ranks&lt;/a&gt;, &lt;a href="https://trackmania.exchange/"&gt;Trackmania Exchange Leaderboards&lt;/a&gt; or &lt;a href="https://www.author-tracker.com/"&gt;Author Medal Tracker&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;✨ With all of these community-made projects around Trackmania, I decided to make my own! ✨&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Presenting... &lt;a href="//www.tmstats.eu"&gt;TMStats&lt;/a&gt;!&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
      &lt;div class="c-embed__cover"&gt;
        &lt;a href="https://www.tmstats.eu/" class="c-link s:max-w-50 align-middle" rel="noopener noreferrer"&gt;
          &lt;img alt="" src="https://res.cloudinary.com/practicaldev/image/fetch/s--91Yq6-Va--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.tmstats.eu/cover_tmstats.png" height="566" class="m-0" width="880"&gt;
        &lt;/a&gt;
      &lt;/div&gt;
    &lt;div class="c-embed__body"&gt;
      &lt;h2 class="fs-xl lh-tight"&gt;
        &lt;a href="https://www.tmstats.eu/" rel="noopener noreferrer" class="c-link"&gt;
          TMStats | Medal Tracker
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;p class="truncate-at-3"&gt;
          Overview of all Trackmania medals you achieved. Share your campaign or daily maps medals with anyone!
        &lt;/p&gt;
      &lt;div class="color-secondary fs-s flex items-center"&gt;
          &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://res.cloudinary.com/practicaldev/image/fetch/s--8y9_mWKH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.tmstats.eu/favicon.png" width="128" height="128"&gt;
        tmstats.eu
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;TMStats is a medal tracker website that allows you to sync your Trackmania medals with a TMStats profile. Alongside the informative map detail modal, TMStats comes with a yearly medal view that really makes you appreciate the time you spent playing the game. Another cool feature I implemented is background map analytics that compares all available information to make an informed guess on the map difficulty, allowing you to focus on a difficulty that you enjoy the most 🔥 Last but not least, TMStats holds a private leaderboard of all profiles submitted to TMStats to allow you to compare against your friends, or world, easily.&lt;/p&gt;

&lt;p&gt;TMStats is nowhere near done! Within the first week after publishing the project, thousands of accounts were created, and people started actively using the website. Based on the feedback I received, I will be adding many more features, such as achievements, daily quests, and profile decorations. Wish me good luck! 🤞&lt;/p&gt;

&lt;p&gt;Project Repository: &lt;a href="https://github.com/meldiron/tmstats"&gt;https://github.com/meldiron/tmstats&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Submission Category:
&lt;/h3&gt;

&lt;p&gt;🧙 Web2 Wizards&lt;/p&gt;

&lt;h3&gt;
  
  
  Link to Code
&lt;/h3&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--566lAguM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Meldiron"&gt;
        Meldiron
      &lt;/a&gt; / &lt;a href="https://github.com/Meldiron/tmstats"&gt;
        tmstats
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A medal calendar that shows overview of all Trackmania medals an user achieved.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;h1&gt;
🥇 TMStats&lt;/h1&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://raw.githubusercontent.com/Meldiron/tmstats/master/static/cover_tmstats.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--OnBRFK1Y--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/Meldiron/tmstats/master/static/cover_tmstats.png" alt="Cover"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
👋 Introduction&lt;/h2&gt;
&lt;p&gt;TMStats is a medal tracker that shows overview of all Trackmania medals an user achieved. The website allows user to share campaign or daily maps medals with anyone.&lt;/p&gt;
&lt;p&gt;Project focuses on multiple aspects and features:&lt;/p&gt;
&lt;ul class="contains-task-list"&gt;
&lt;li class="task-list-item"&gt;
 Track of the day (TOTD) medals in yearly view&lt;/li&gt;
&lt;li class="task-list-item"&gt;
 Campaign medals in season view&lt;/li&gt;
&lt;li class="task-list-item"&gt;
 Custom map medals in list view&lt;/li&gt;
&lt;li class="task-list-item"&gt;
 Gamified achievement &amp;amp; quests system&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As of right now, project has no business model and is fully free and open-sourced. Project generates expenses, and business model might be added in the future.&lt;/p&gt;
&lt;h2&gt;
🤖 Tech Stack&lt;/h2&gt;
&lt;p&gt;TMStats uses multiple frontend and backend technologies with focus of simplifying the development. Main focus of tech stack in this project is to make development fast and fun, instead of making it scalable and reliable.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://tailwindcss.com/" rel="nofollow"&gt;TailwindCSS&lt;/a&gt;, a CSS library to rapidly design components using HTML classes&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://svelte.dev/" rel="nofollow"&gt;Svelte&lt;/a&gt;, a JS library to build reactive frontend…&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Meldiron/tmstats"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h3&gt;
  
  
  Additional Resources / Info
&lt;/h3&gt;

&lt;p&gt;I enjoyed Hackathon &lt;strong&gt;A LOT&lt;/strong&gt;, but I am an Appwrite maintainer, and it would not be fair if I competed for winnings. So I will not 😇 I submitted a project to share the project I am proud of, get participation rewards (shiny Dev badge), and show the community what Appwrite can be used for! 💪&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tech Stack:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🔥 &lt;a href="https://appwrite.io/"&gt;Appwrite&lt;/a&gt; 🔥&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://svelte.dev/"&gt;Svelte&lt;/a&gt; &amp;amp; &lt;a href="https://kit.svelte.dev/"&gt;Svelte Kit&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tailwindcss.com/"&gt;TailwindCSS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://deno.land/"&gt;Deno&lt;/a&gt; (in Appwrite Functions)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/scottbedard/svelte-heatmap"&gt;Svelte Heatmap&lt;/a&gt; (Svelte plugin)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Collaborators:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://dev.to/benji47"&gt;Benko&lt;/a&gt;; Huge help with API discovery statistic formulas and testing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Screenshots:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;User Profile&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--eRVR7vix--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mq1rouctjztvoh6nhzgb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--eRVR7vix--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mq1rouctjztvoh6nhzgb.png" alt="User Profile" width="880" height="495"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Map Details&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--9JwbRqvo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cjf7y22342z38eztyc6k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9JwbRqvo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cjf7y22342z38eztyc6k.png" alt="Map Details" width="880" height="463"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Homepage&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--U5UjsBHn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/we4dxoyvx9xwfu0uvitf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--U5UjsBHn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/we4dxoyvx9xwfu0uvitf.png" alt="Homepage" width="880" height="495"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>appwritehack</category>
      <category>opensource</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Why Discord Is a Must-Have for OSS</title>
      <dc:creator>Matej Bačo</dc:creator>
      <pubDate>Tue, 03 May 2022 10:46:17 +0000</pubDate>
      <link>https://dev.to/appwrite/why-discord-is-a-must-have-for-oss-2jpj</link>
      <guid>https://dev.to/appwrite/why-discord-is-a-must-have-for-oss-2jpj</guid>
      <description>&lt;p&gt;&lt;a href="https://appwrite.io/"&gt;Appwrite&lt;/a&gt; is an OSS company, and we share as much as possible with our community. Since transparency is both a value and a mindset for us, we share articles like these to provide knowledge that might help developers all around the world.&lt;/p&gt;

&lt;p&gt;Today we will focus on the topic of handling community around your open-source project outside of GitHub.&lt;/p&gt;

&lt;p&gt;Don’t get me wrong, the GitHub issue and discussion section is a great place for keeping track of topics, but we noticed people consider these too “official”. From our experience, we see questions about failing installations more often on Discord than on GitHub. The reason behind that is that people think they must have done something wrong, and don't want to “spam” GitHub with issues that will be resolved with 1 comment. Instead, they visit Discord and freely chat about the problem they encountered.&lt;/p&gt;

&lt;p&gt;Before looking at the advantages of the Discord community for an open-source project, let’s quickly talk about Discord!&lt;/p&gt;

&lt;h2&gt;
  
  
  🤖 What Is Discord?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://discord.com/"&gt;Discord&lt;/a&gt; is a place for any kind of community to chat or talk. Many different communities use Discord nowadays, such as school classes, gaming communities, companies, or even your group of close friends. You can be a member of one or many communities, and set up a different profile for each of them.&lt;/p&gt;

&lt;p&gt;Open-source projects love Discord, because of its nature of being interactive. With Discord live chat, people tend to expect answers within a few hours or minutes, which gives them a great opportunity to ask questions about a bug, specific feature, or approach to a problem using your project. Without Discord, many developers might leave your project frustrated from a mistake they made instead of asking for help on GitHub.&lt;/p&gt;

&lt;p&gt;With a clear picture of what Discord is, let’s jump into the reasons why developers and maintainers tend to use the Discord server.&lt;/p&gt;

&lt;h2&gt;
  
  
  💭 Collect Feedback
&lt;/h2&gt;

&lt;p&gt;Feedback is an important, if not the most important tool for an open-source project. This comes down to the reason you decided to make a project open-source! OSS allows others to not only use it but also help you improve the quality of the project by requesting or adding features they are interested in.&lt;/p&gt;

&lt;p&gt;As mentioned earlier, GitHub discussions would be the best place for keeping track of feedback about a project, but developers find these channels too formal for pitching a feature they need for their project. With Discord, the maintainer can set up a dedicated room called &lt;code&gt;#ideas&lt;/code&gt; and let developers freely chat about any idea they have. Some will be already implemented, and some will be out of the scope of your project... There will also be some ideas that don’t receive a lot of attention, meaning the rest of the community doesn't find them too useful. On the other hand, if an idea receives dozens of reactions, you can easily assume this feature would be appreciated by many developers. A lot of the tiny features in Appwrite come from our Discord ideas channel, and they make the project way more interesting.&lt;/p&gt;

&lt;h2&gt;
  
  
  🙌 Provide Support
&lt;/h2&gt;

&lt;p&gt;With every new attempt to use your tool, developers can face a new issue, an undocumented edge case, or even an issue with their specific hardware. All of these are close to impossible for developers to debug on their own, as they are not familiar with your source code.&lt;/p&gt;

&lt;p&gt;What happens next? Developer hops into your Discord server, look for a &lt;code&gt;#support&lt;/code&gt; room and ask the question. You can get an enormous amount of positive feedback (and GitHub stars 😈) if you provide quick support to a developer. You also prevent a new developer from losing interest in your project without actually trying it, because some bug didn't let them. Last but not least, reading “Thank you” or “You are great” from time to time feels amazing!&lt;/p&gt;

&lt;p&gt;As your community grows, you will notice that some community members that were asking questions a few weeks ago are now answering the questions of newcomers. These are usually people that really enjoyed using your project, and want to stick with it. There are so many reasons why people do this, and why you should value those, it deserves its own article! The most common reason to provide such support is to improve their own skill with your project, as providing support forces you to think about problems you likely never encountered before. On Appwrite Discord, we have a few of these, and if I see them having any questions, I instantly jump in to help them. In the end, that is the least I can do to show appreciation for their hard work.&lt;/p&gt;

&lt;h2&gt;
  
  
  👨‍💻 Hire New Maintainers
&lt;/h2&gt;

&lt;p&gt;People who actively chat on your Discord should already be considered contributors! They know (and use) your project, they see many use-cases, they hear a bunch of feedback, and they perhaps even created a PR or two for you. If at any point your budget allows you to hire a maintainer, your active community members should be the first developers you consider. They not only share the same passion for the project as you, but they are also more likely to quickly get familiar with the source code. Fun fact, all of the early Appwrite hires were active contributors 🤯&lt;/p&gt;

&lt;h2&gt;
  
  
  📃 Share Development Updates
&lt;/h2&gt;

&lt;p&gt;Again, going back to the roots of the mindset behind an open-source project, you want to be as transparent as possible, to get feedback for features you are working on. With no interest shown by the community in the feature you are developing, you might as well consider delaying it for another release. &lt;/p&gt;

&lt;p&gt;Discord is a great channel for sharing those updates, as it automatically sends a notification to members. These development notes show that you actively work on a project, allow you to get feedback even before a feature is implemented, and help with building interest in your next release. At Appwrite, we started doing weekly community updates some time ago, and we already received a lot of positive feedback for doing it.&lt;/p&gt;

&lt;h2&gt;
  
  
  🧪 Find Alpha Testers
&lt;/h2&gt;

&lt;p&gt;Developers &lt;strong&gt;love&lt;/strong&gt; new features! By preparing an alpha build and sharing it with your community, you can easily get testers for your next release. This helps you prevent bugs before releasing a big feature and keep your application stable for production use. It also helps you fine-tune the feature, while still having the option to do breaking changes. We at Appwrite hosted many alpha tests for big features, such as Database refactor and Cloud Functions Gen-2.&lt;/p&gt;

&lt;h2&gt;
  
  
  📈 New KPIs
&lt;/h2&gt;

&lt;p&gt;In an open-source project that wants to get funds for growth, the main strategy is to find an investor. Having a Discord community can provide a lot of insightful metrics to share with a potential investor for evaluation of your project. Of course, it’s not as simple as showing cool numbers, but having a gradually increasing amount of new members, active members or messages can all be key performance indicators for the investor to make a decision. Having an active Discord community can also help you fill in a slide or two with some heart-warming feedback messages to show proper validation of your project in the real world.&lt;/p&gt;

&lt;h2&gt;
  
  
  👨‍🎓 Conclusion
&lt;/h2&gt;

&lt;p&gt;A Discord server can be a really powerful communication channel for any open-source project if used wisely. Discord server can help with collecting feedback, providing quick support, building new contributor relationships, getting releases tested, and building a community in general. With all advantages, the Discord server can provide, keep in mind that some new problems can arise. For us, the biggest challenge was to actively engage with our GitHub issues alongside Discord. We had to start requesting people to open GitHub issues for problems they report on Discord, otherwise, we would forget about them. &lt;/p&gt;

&lt;h2&gt;
  
  
  📚 Learn More
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://appwrite.io/"&gt;Appwrite&lt;/a&gt; is an open-source Backend-as-a-Service (BaaS), packaged as a set of Docker microservices, to give developers of any background the tools necessary to build modern apps quickly and securely. Chat with us on &lt;a href="https://appwrite.io/discord"&gt;Discord&lt;/a&gt;, or learn more about Appwrite:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🚀 &lt;a href="https://github.com/appwrite"&gt;Appwrite Github&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📜 &lt;a href="https://appwrite.io/docs"&gt;Appwrite Docs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;💬 &lt;a href="https://appwrite.io/discord"&gt;Discord Community&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>discord</category>
      <category>opensource</category>
      <category>github</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Start Selling Online Using Appwrite and Stripe</title>
      <dc:creator>Matej Bačo</dc:creator>
      <pubDate>Thu, 17 Mar 2022 22:14:47 +0000</pubDate>
      <link>https://dev.to/appwrite/start-selling-online-using-appwrite-and-stripe-3l04</link>
      <guid>https://dev.to/appwrite/start-selling-online-using-appwrite-and-stripe-3l04</guid>
      <description>&lt;p&gt;&lt;a href="https://appwrite.io/" rel="noopener noreferrer"&gt;Appwrite&lt;/a&gt; is an open source backend-as-a-service that abstracts all the complexity involved in building a modern application by providing you with a set of REST APIs for your core backend needs. Appwrite handles user authentication and authorization, realtime databases, cloud functions, webhooks, and much more! If anything is missing, you can easily extend Appwrite using your favorite backend language.&lt;/p&gt;

&lt;p&gt;Every website is a unique project with unique requirements. Appwrite understands the need for backend customization and provides &lt;a href="https://appwrite.io/docs/functions" rel="noopener noreferrer"&gt;Appwrite Functions&lt;/a&gt; to allow you to do exactly that. Thanks to 7 supported programming languages and more coming soon, Appwrite lets you use the language you are the most familiar with for your backend needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  🚀 What Is New in Appwrite 0.13
&lt;/h2&gt;

&lt;p&gt;Appwrite 0.13 introduced new features regarding storage, CLI, and functions. With a fully rewritten execution model, Appwrite now allows you to run functions as quick as 1 millisecond! 🤯 With such a performance, Appwrite now also ships with &lt;strong&gt;synchronous execution&lt;/strong&gt;, allowing you to execute a function and get a response within one HTTP request.&lt;/p&gt;

&lt;p&gt;Thanks to the new execution model, Appwrite 0.13 also allows the creation of global variables to share cache between multiple executions of a function. This drastically improves the performance of real-world applications, as they only need to load dependencies, initiate 3rd party communication, and pre-load resources in the first execution.&lt;/p&gt;

&lt;p&gt;Last but not least, Appwrite got a new build process that is capable of downloading your project dependencies on the server side, so you no longer need to deploy your function with dependencies. This makes the flow much simpler, deployments smaller, and developers happier 😍&lt;/p&gt;

&lt;h2&gt;
  
  
  💸 Online Payments with Stripe
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://stripe.com/" rel="noopener noreferrer"&gt;Stripe&lt;/a&gt; is a huge set of payments products that allows you to do business online. Stripe allows you to receive payments online, set up and manage subscriptions, automatically include taxes into payments, manage the online receipt, generate a checkout page, and much more. Stripe proudly supports multiple payment methods and countries, which makes it one of the best options for any online product.&lt;/p&gt;

&lt;p&gt;Stripe is loved by developers too! Online payments are hard… Stripe managed to create a fast, secure and easy to understand environment that lets developers set up online payments within a few minutes.&lt;/p&gt;

&lt;p&gt;From a technical point of view, Stripe payments are initiated using REST API, and payment status updates are sent through webhooks. Let’s look at how you can use Appwrite Functions to securely communicate with Stripe to implement online payments into an application.&lt;/p&gt;

&lt;h2&gt;
  
  
  🙋 What Is a Webhook?
&lt;/h2&gt;

&lt;p&gt;Webhook is an HTTP request sent from one server to another, to inform it about some change. Webhooks are a smarter alternative to pulling data through an API server if you need to quickly adapt to an external change.&lt;/p&gt;

&lt;p&gt;Stripe uses webhooks to inform applications about changes to the status of a payment. For a second let’s imagine webhooks were not implemented in Stripe, how would you know a payment was successful? For each ongoing payment, you would need to have a loop to send API requests to get payment status every few seconds and don’t stop until you have a status change. As you can imagine, this would be a resource-consuming solution that wouldn’t scale well with many payments pending at the same time, hitting API limits in the worst scenarios. Thanks to webhooks, you can give Stripe an URL, and the Stripe server will hit the URL with an HTTP request, providing a bunch of data about what has changed.&lt;/p&gt;

&lt;p&gt;Similarly to Stripe, Appwrite also supports webhooks and can trigger HTTP requests when a change occurs inside Appwrite, such as a new user registered, or a database change. That means Appwrite can send out webhooks, but can it receive one? 🤔&lt;/p&gt;

&lt;h2&gt;
  
  
  🪝 Appwrite Webhook Proxy
&lt;/h2&gt;

&lt;p&gt;Appwrite can receive webhook requests by default, thanks to Appwrite Functions. There is an endpoint in Appwirte HTTP API that can create a function execution. This method allows passing data in, and also providing request headers. That’s all you need for such a webhook listener, but there is one small hiccup.&lt;/p&gt;

&lt;p&gt;Looking at &lt;a href="https://appwrite.io/docs/server/functions?sdk=nodejs-default#functionsCreateExecution" rel="noopener noreferrer"&gt;Appwrite documentation&lt;/a&gt;, it expects a JSON body where all data is stringified under the &lt;code&gt;data&lt;/code&gt; key. On the other hand, looking at &lt;a href="https://stripe.com/docs/webhooks#check-event-objects" rel="noopener noreferrer"&gt;Stripe documentation&lt;/a&gt;, it sends a webhook with all data in the root level as a JSON object.&lt;/p&gt;

&lt;p&gt;Alongside this schema miss-match, Appwrite also expects some custom headers (such as API key), which Stripe cannot send. This problem can be solved by a simple proxy server that can properly map between these two schemas, and apply authentication headers.&lt;/p&gt;

&lt;p&gt;You can expect official implementation from Appwrite itself, but as of right now, you can use &lt;a href="https://github.com/Meldiron/appwrite-webhook-proxy" rel="noopener noreferrer"&gt;Meldiron’s Appwrite webhook proxy&lt;/a&gt;. This project adds a configuration into your Appwrite setup that defined a new &lt;code&gt;/v1/webhook-proxy&lt;/code&gt; endpoint in Appwrite API to solve the problem from earlier. Later in the article, we will take a look at how to set up this webhook proxy, and how to connect it to Stripe.&lt;/p&gt;

&lt;h2&gt;
  
  
  🛒 Let’s Code a Store
&lt;/h2&gt;

&lt;p&gt;To present Stripe integration in Appwrite, I decided to create a simple application &lt;strong&gt;cookie store&lt;/strong&gt;, where a customer can buy one of two cookie packs. After payment, users can look at their order history and see a payment status. This is a minimal implementation that does not include invoicing, fulfillment, or any eCommerce logic. The project was made with simplicity in mind to serve as a learning resource for anyone integrating Stripe into their Appwrite projects.&lt;/p&gt;

&lt;p&gt;The application was made using &lt;a href="https://nuxtjs.org/" rel="noopener noreferrer"&gt;NuxtJS&lt;/a&gt; framework with &lt;a href="https://www.typescriptlang.org/" rel="noopener noreferrer"&gt;TypeScript&lt;/a&gt;, and &lt;a href="https://tailwindcss.com/" rel="noopener noreferrer"&gt;Tailwind CSS&lt;/a&gt; for designing utility classes. You can follow along, or download the source code from the &lt;a href="https://github.com/Meldiron/appwrite-cookie-store" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stripe Setup
&lt;/h3&gt;

&lt;p&gt;Let’s start by properly setting up our Stripe account, to make sure we have all secrets we might need in the future. For this example, we will be using test mode, but the same steps could be followed in production mode.&lt;/p&gt;

&lt;p&gt;You start by visiting the &lt;a href="https://stripe.com/" rel="noopener noreferrer"&gt;Stripe&lt;/a&gt; website and signing up. Once in the dashboard, you can switch to the &lt;code&gt;Developers&lt;/code&gt; page and enter the &lt;code&gt;API keys&lt;/code&gt; tab. In there, you click the &lt;code&gt;Reval key&lt;/code&gt; button and copy this key. It will be used later when setting up &lt;code&gt;createPayment&lt;/code&gt; function in Appwrite.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0wq32elfcjnm7yza4bec.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0wq32elfcjnm7yza4bec.png" alt="Stripe API key"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, let’s switch to the &lt;code&gt;Webhooks&lt;/code&gt; tab, and set up a new endpoint. When adding an endpoint, make sure to use URL &lt;code&gt;http://YOR_ENDPOINT/v1/webhook-proxy&lt;/code&gt;, and provide any description you want. Last but not least, you select events to listen to, in the case of simple online payment, you only need events &lt;code&gt;payment_intent.succeeded&lt;/code&gt; and &lt;code&gt;payment_intent.canceled&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcl96puoj187q0hkye67g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcl96puoj187q0hkye67g.png" alt="Stripe webhook setup"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After adding the endpoint, copy your &lt;code&gt;Signing secret&lt;/code&gt;, as you will need this in &lt;code&gt;updatePayment&lt;/code&gt; Appwrite Function later.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frpb5rco54av986ehhvc0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frpb5rco54av986ehhvc0.png" alt="Stripe webhook secret"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Appwrite Project Setup
&lt;/h3&gt;

&lt;p&gt;Before diving into frontend development, you first set up the Appwrite project. After following &lt;a href="https://appwrite.io/docs/installation" rel="noopener noreferrer"&gt;installation instructions&lt;/a&gt; and signing up, you can create a project with a custom project ID &lt;code&gt;cookieShop&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmex5yhnx8d9pqa0fvwlv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmex5yhnx8d9pqa0fvwlv.png" alt="new Appwrite project"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once the project is created, let’s hop into the &lt;code&gt;Services&lt;/code&gt; tab on the &lt;code&gt;Settings&lt;/code&gt; page. Here you can easily disable services that you won’t be using in our project. In your application, you will only be using account, database and function services. Make sure to keep this enabled, and disable the rest.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsz8chvd220xm96uopxln.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsz8chvd220xm96uopxln.png" alt="Appwrite project service settings"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Last but not least, let’s open the &lt;code&gt;Settings&lt;/code&gt; tab on the &lt;code&gt;Users&lt;/code&gt; page. Here you can disable all authentication methods except anonymous session, as this will be the only one your application will use.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F18srpfc49tkwqwjgwjn5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F18srpfc49tkwqwjgwjn5.png" alt="Appwrite users settings"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With all of these configurations in place, your Appwrite project is ready! 🎉&lt;/p&gt;

&lt;p&gt;Now, you need to apply programmatic setup from the cookie store &lt;a href="https://github.com/Meldiron/appwrite-cookie-store" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt; that sets up database structure and prepares Appwrite Functions. After cloning the repository and setting up &lt;a href="https://appwrite.io/docs/command-line" rel="noopener noreferrer"&gt;Appwrite CLI&lt;/a&gt;, all you need to do is to run &lt;code&gt;appwrite deploy –all&lt;/code&gt; to apply all of the programmatic setups. If you are interested in understanding the underlying code of these Appwrite Functions, you can check them out in respective folders:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/Meldiron/appwrite-cookie-store/tree/master/functions/createPayment" rel="noopener noreferrer"&gt;createPayment&lt;/a&gt; (NodeJS)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/Meldiron/appwrite-cookie-store/tree/master/functions/updatePayment" rel="noopener noreferrer"&gt;updatePayment&lt;/a&gt; (NodeJS)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once these functions are deployed, you need to set their environment variables. You visit &lt;code&gt;Functions&lt;/code&gt; in your Appwrite Console and open up the &lt;code&gt;Settings&lt;/code&gt; tab of your &lt;code&gt;createPayment&lt;/code&gt; function. In there, near the end of the settings, you need to add a variable called &lt;code&gt;STRIPE_KEY&lt;/code&gt; with your secret key from the Stripe dashboard. Next, you switch to settings of &lt;code&gt;updatePayment&lt;/code&gt; and set up a few environments variables there:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;STRIPE_SIGNATURE&lt;/code&gt; - Webhook signature key from Stripe dashboard.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;APPWRITE_FUNCTION_ENDPOINT&lt;/code&gt; - Endpoint of your Appwrite instance, found in &lt;code&gt;Settings&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;APPWRITE_FUNCTION_API_KEY&lt;/code&gt; - Appwrite project API key. You can generate one in the left menu.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With that configured, let’s see how our Appwrite Functions actually work! 💻&lt;/p&gt;

&lt;h3&gt;
  
  
  Appwrite Functions
&lt;/h3&gt;

&lt;p&gt;To better understand our Appwrite Functions logic, let’s look at their source code. Both functions are written in Node.JS&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Create Payment
&lt;/h4&gt;

&lt;p&gt;First of all, you add Stripe library to our code, as you will be creating a payment in this function:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stripe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;stripe&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Next, you set up a variable holding all possible packs (products), and their basic information:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;packages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pack1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Medium Cookie Pack&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Package incluces 1 cookie&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.99&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;preview&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/pack1.jpg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pack2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Large Cookie Pack&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Package incluces 6 cookies&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;4.99&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;preview&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/pack2.jpg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You continue by setting up a function that will get executed when an execution is created:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Future code goes in here&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Inside your function, let’s make sure function as properly configured in Appwrite, and provides required environment variables:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;

  &lt;span class="c1"&gt;// Setup&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;STRIPE_KEY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Environment variables are not set.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Next, let’s validate user input - payload:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;

  &lt;span class="c1"&gt;// Prepate data&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stripeClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;stripe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;STRIPE_KEY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="kr"&gt;package&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;packages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;pack&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;pack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;packId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="kr"&gt;package&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Could not find the pack.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You continue by creating a Stripe payment session:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;

  &lt;span class="c1"&gt;// Create Stripe payment&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;stripeClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;checkout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sessions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;line_items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;price_data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;eur&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;product_data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;package&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;package&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="na"&gt;unit_amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;package&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;payment&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;success_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;redirectSuccess&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;cancel_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;redirectFailed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;payment_intent_data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;APPWRITE_FUNCTION_USER_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;packageId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;package&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Last but not least, let’s return stripe payment session URL, so client can be redirected to the payment:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;

  &lt;span class="c1"&gt;// Return redirect URL&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;paymentUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h4&gt;
  
  
  2. Update Payment
&lt;/h4&gt;

&lt;p&gt;Similar to our first function, you require libraries and set up a main function:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stripe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;stripe&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sdk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node-appwrite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Future code goes in here&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Did you notice you imported Appwrite this time? That’s right! This function is executed by Stripe webhook when a payment session status changes. This means, you will need to update the Appwrite document with a new status, so you need a proper connection with the API.&lt;/p&gt;

&lt;p&gt;Anyway, you continue by validating environment variables, but this time you also initialize Appwrite SDK:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;

  &lt;span class="c1"&gt;// Setup Appwrite SDK&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;sdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;database&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;sdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;APPWRITE_FUNCTION_ENDPOINT&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
    &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;APPWRITE_FUNCTION_API_KEY&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
    &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;STRIPE_SIGNATURE&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Environment variables are not set.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;client&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setEndpoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;APPWRITE_FUNCTION_ENDPOINT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setProject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;APPWRITE_FUNCTION_PROJECT_ID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;APPWRITE_FUNCTION_API_KEY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Next, let’s parse the function input (payload), and validate it using Stripe:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;

  &lt;span class="c1"&gt;// Prepate data&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stripeSignature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;STRIPE_SIGNATURE&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// Validate request + authentication check&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;stripe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;webhooks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;constructEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;stripe-signature&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="nx"&gt;stripeSignature&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Furthermore, you can parse data from Stripe event and pick information relevant to your usage:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;

  &lt;span class="c1"&gt;// Prepare results&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;payment_intent.succeeded&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;success&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;payment_intent.canceled&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;failed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;unknown&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;charges&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;packId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;charges&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;packageId&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;paymentId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;packId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;paymentId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;To finish it off, let’s add a logic to update or create a document, depending on if it already exists or not:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;

  &lt;span class="c1"&gt;// Check if document already exists&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;existingDocuments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listDocuments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;orders&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;`paymentId.equal('&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;paymentId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;')`&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;outcome&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;existingDocuments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Document already exists, update it&lt;/span&gt;
    &lt;span class="nx"&gt;outcome&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;updateDocument&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;updateDocument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;orders&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;existingDocuments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documents&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;`user:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Document doesnt exist, create one&lt;/span&gt;
    &lt;span class="nx"&gt;outcome&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;createDocument&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createDocument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;orders&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;unique()&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;`user:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Finally, let’s return what you just did as a response, so you can inspect execution response in Appwrite Console when you need to double-check what happened in some specific payment:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;

  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;outcome&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Appwrite Webhook Proxy
&lt;/h3&gt;

&lt;p&gt;As mentioned earlier, you will need to use &lt;a href="https://github.com/Meldiron/appwrite-webhook-proxy" rel="noopener noreferrer"&gt;Meldiron’s webhook proxy&lt;/a&gt; to translate Stripe’s schema to a schema that Appwrite API supports. To do that, you will add a new container into the Appwrite Docker containers stack, which will add a new endpoint to Appwrite API.&lt;/p&gt;

&lt;p&gt;Let’s start by adding a new container definition inside the &lt;code&gt;docker-compose.yml&lt;/code&gt; file in an &lt;code&gt;appwrite&lt;/code&gt; folder:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3"&lt;/span&gt;

&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;appwrite-webhook-proxy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;meldiron/appwrite-webhook-proxy:v0.0.4&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;appwrite-webhook-proxy&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.enable=true"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.constraint-label-stack=appwrite"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.docker.network=appwrite"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.http.services.appwrite_webhook_proxy.loadbalancer.server.port=4444"&lt;/span&gt;
      &lt;span class="c1"&gt;# http&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;traefik.http.routers.appwrite_webhook_proxy_http.entrypoints=appwrite_web&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;traefik.http.routers.appwrite_webhook_proxy_http.rule=PathPrefix(`/v1/webhook-proxy`)&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;traefik.http.routers.appwrite_webhook_proxy_http.service=appwrite_webhook_proxy&lt;/span&gt;
      &lt;span class="c1"&gt;# https&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;traefik.http.routers.appwrite_webhook_proxy_https.entrypoints=appwrite_websecure&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;traefik.http.routers.appwrite_webhook_proxy_https.rule=PathPrefix(`/v1/webhook-proxy`)&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;traefik.http.routers.appwrite_webhook_proxy_https.service=appwrite_webhook_proxy&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;traefik.http.routers.appwrite_webhook_proxy_https.tls=true&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;traefik.http.routers.appwrite_webhook_proxy_https.tls.certresolver=dns&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;appwrite&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;appwrite&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;WEBHOOK_PROXY_APPWRITE_ENDPOINT&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;WEBHOOK_PROXY_APPWRITE_PROJECT_ID&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;WEBHOOK_PROXY_APPWRITE_API_KEY&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;WEBHOOK_PROXY_APPWRITE_FUNCTION_ID&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;span class="c1"&gt;# ...&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;With this in place, a new proxy server will be listening on &lt;code&gt;/v1/webhook-proxy&lt;/code&gt; endpoint.&lt;/p&gt;

&lt;p&gt;Now let’s update the &lt;code&gt;.env&lt;/code&gt; file in the same &lt;code&gt;appwrite&lt;/code&gt; folder to add authentication variables this container needs for proper secure communication:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

WEBHOOK_PROXY_APPWRITE_ENDPOINT=https://YOUR_ENDPOINT/v1
WEBHOOK_PROXY_APPWRITE_PROJECT_ID=YOUR_PROJECT_ID
WEBHOOK_PROXY_APPWRITE_API_KEY=YOUR_API_KEY
WEBHOOK_PROXY_APPWRITE_FUNCTION_ID=updatePayment


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Finally, let’s spin up the container by running &lt;code&gt;docker-compose up -d&lt;/code&gt;. With all of that in place, you can now point Stripe to &lt;code&gt;https://YOR_ENDPOINT/v1/webhook-proxy&lt;/code&gt; , and Stripe will start executing your &lt;code&gt;updatePayment&lt;/code&gt; function while providing all data in a proper schema.&lt;/p&gt;

&lt;h3&gt;
  
  
  Frontend Website Setup
&lt;/h3&gt;

&lt;p&gt;A process of designing frontend is not the focus of this article, so if you are interested in details of implementation, make sure to check out the &lt;a href="https://github.com/Meldiron/appwrite-cookie-store" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt; of this project.&lt;/p&gt;

&lt;p&gt;With that out of the way, let’s look at communication between the frontend and Appwrite project. All of this communication is implemented in a separated &lt;a href="https://github.com/Meldiron/appwrite-cookie-store/blob/master/services/appwrite.ts" rel="noopener noreferrer"&gt;appwrite.ts&lt;/a&gt; file that holds functions for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Authentication&lt;/li&gt;
&lt;li&gt;Payment&lt;/li&gt;
&lt;li&gt;Order History&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before coding functions for these services, let’s set up our service file and do all of the initial setups:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Appwrite&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Models&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;appwrite&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;appwriteEndpoint&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;appwriteProjectId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Appwrite environment variables not properly set!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sdk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Appwrite&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;sdk&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setEndpoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;appwriteEndpoint&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setProject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;appwriteProjectId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;appUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;packId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;paymentId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;Models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Document&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Let’s start by creating trio of the most important authentication functions. You will need one to login, one to log out, and one to check if visitor is logged in. All of this can be done within a few lines of code when using AppwriteSDK:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;AppwriteService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;logout&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deleteSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;current&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Something went wrong. Please try again later.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createAnonymousSession&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;getAuthStatus&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="c1"&gt;// Future code goes in here&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Next, you create a function that will trigger our previously coded &lt;code&gt;createPayment&lt;/code&gt; Appwrite Function, and use &lt;code&gt;url&lt;/code&gt; from the response to redirect user to Stripe, where they can pay they order:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;buyPack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;packId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;executionResponse&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;functions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createExecution&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;createPayment&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
                &lt;span class="na"&gt;redirectSuccess&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;appUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/cart-success`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;redirectFailed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;appUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/cart-error`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="nx"&gt;packId&lt;/span&gt;
            &lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;executionResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;completed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;executionResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stdout&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;executionResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;executionResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;paymentUrl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Something went wrong. Please try again later.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Last but not least, let's implement a method to get user’s order history that supports offset pagination:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;getOrders&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DocumentList&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;offset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ordersResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;listDocuments&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;orders&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;createdAt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DESC&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;ordersResponse&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Something went wrong. Please try again later.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;With all of this login in place, all you need to do is to finish off the rest of the frontend application by creating pages, components, and hooking into our &lt;code&gt;AppwriteService&lt;/code&gt; to talk to the Appwrite backend.&lt;/p&gt;

&lt;p&gt;You have just successfully created your very own store using Appwrite and Stripe! 👏 If there are any concerns about skipped parts of the frontend code, and I can’t stress this enough, make sure to check out the whole &lt;a href="https://github.com/Meldiron/appwrite-cookie-store" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt; of this project, that holds a fully working demo application. There are some screenshots too! 👀&lt;/p&gt;

&lt;h2&gt;
  
  
  👨‍🎓 Conclusion
&lt;/h2&gt;

&lt;p&gt;The ability to integrate your application with 3rd party tools and APIs can become critical for any scalable application. Thanks to Appwrite 0.13, as you just experienced, Appwrite Functions can now communicate both ways, allowing you to prepare your projects without any limitations. This not only means you can implement pretty much any payment gateway into your Appwrite project, but it also means all of you can enjoy preparing your Appwrite-based applications without any limitations!&lt;/p&gt;

&lt;p&gt;If you have a project to share, need help or simply want to become a part of the Appwrite community, I would love for you to join our official Appwrite &lt;a href="https://appwrite.io/discord" rel="noopener noreferrer"&gt;Discord server&lt;/a&gt;. I can’t wait to see what you build!&lt;/p&gt;

&lt;h2&gt;
  
  
  📚 Learn More
&lt;/h2&gt;

&lt;p&gt;You can use the following resources to learn more and get help:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🚀 &lt;a href="https://github.com/appwrite" rel="noopener noreferrer"&gt;Appwrite Github&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📜 &lt;a href="https://appwrite.io/docs" rel="noopener noreferrer"&gt;Appwrite Docs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;💬 &lt;a href="https://appwrite.io/discord" rel="noopener noreferrer"&gt;Discord Community&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>showdev</category>
      <category>tutorial</category>
      <category>webdev</category>
      <category>saas</category>
    </item>
    <item>
      <title>Scale Appwrite Storage with DigitalOcean Spaces</title>
      <dc:creator>Matej Bačo</dc:creator>
      <pubDate>Wed, 16 Mar 2022 04:14:00 +0000</pubDate>
      <link>https://dev.to/appwrite/scale-appwrite-storage-with-digitalocean-spaces-36kh</link>
      <guid>https://dev.to/appwrite/scale-appwrite-storage-with-digitalocean-spaces-36kh</guid>
      <description>&lt;p&gt;&lt;a href="https://appwrite.io/"&gt;Appwrite&lt;/a&gt; is an open source backend-as-a-service that abstracts all the complexity involved in building a modern application by providing you with a set of REST APIs for your core backend needs. Appwrite handles user authentication and authorization, realtime databases, cloud functions, webhooks, and much more! If anything is missing, you can easily extend Appwrite using your favorite backend language.&lt;/p&gt;

&lt;p&gt;One of the core functionalities of Appwrite is &lt;a href="https://appwrite.io/docs/storage"&gt;Appwrite Storage&lt;/a&gt;. It allows you to upload, view, download, and query your project files. Appwrite Storage not only takes care of encryption, compression and antivirus scans, it’s also built on top of Appwrite’s flexible yet simple permission system. Appwrite lets you store any files such as text documents, icons, images, videos and more.&lt;/p&gt;

&lt;h2&gt;
  
  
  🚀 What is new in Appwrite 0.13
&lt;/h2&gt;

&lt;p&gt;The Appwrite 0.13 release offers a much more powerful Storage service! You can now group your files into &lt;strong&gt;Storage Buckets&lt;/strong&gt; for better organization, as well as better control over allowed size and extension. On each bucket, you can also specify a different set of permissions, and toggle additional features such as encryption and compression.&lt;/p&gt;

&lt;p&gt;Additionally, you can now upload files with no size limitation. In this release, &lt;strong&gt;chunked upload&lt;/strong&gt; is supported and you can split your file into 5MB chunks to gradually upload a file as large as your bucket settings allow. Don’t worry, chunk upload logic is part of our SDKs and if it sees a file larger than 5MB, chunk upload will automatically kick in. One more secret! Our SDKs now accept an &lt;code&gt;onProgress&lt;/code&gt; callback that will be triggered each time a new chunk is successfully processed. Implementing progress bars have never been easier 💪&lt;/p&gt;

&lt;p&gt;Finally, Appwrite Storage is no longer limited by the size of your hard drive! With Appwrite 0.13, you can now connect the Storage service with cloud storage providers such as &lt;a href="https://www.digitalocean.com/products/spaces"&gt;DigitalOcean Spaces&lt;/a&gt; or &lt;a href="https://aws.amazon.com/s3/"&gt;Amazon S3&lt;/a&gt;. With the introduction of these adapters, you no longer need to worry about running out of space due to Appwrite Storage. More providers are coming soon, so stay tuned 😎 In this guide, we’ll take a look at setting up Appwrite Storage using DigitalOcean Spaces as the storage adapter.&lt;/p&gt;

&lt;h2&gt;
  
  
  🌊 DigitalOcean Spaces setup
&lt;/h2&gt;

&lt;p&gt;Before we jump into Appwrite, let’s prepare your DigitalOcean Space. After logging into DigitalOcean:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Click the green &lt;code&gt;Create&lt;/code&gt; button in the upper right corner and select &lt;code&gt;Spaces&lt;/code&gt; from the dropdown. &lt;/li&gt;
&lt;li&gt;You will be redirected to the &lt;code&gt;Create Space&lt;/code&gt; page, where we need to configure your file storage. Region and CDN configuration are up to you, but in the &lt;code&gt;File listing&lt;/code&gt; section, you need to pick &lt;code&gt;Restrict File Listing&lt;/code&gt;. This keeps our files secured from the internet, and only communication between servers will be allowed. &lt;/li&gt;
&lt;li&gt;Before we finish the setup, provide your space with a unique name. Quick tip, this name will only be stored in the &lt;code&gt;.env&lt;/code&gt; file of your Appwrite instance, and users will never see it. That means, the name can be as simple or as complex as you want.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9fowbyfi54txdqdxbzpw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9fowbyfi54txdqdxbzpw.png" alt="Create a Space" width="800" height="471"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;After creating the space, you will be presented with a space endpoint. Make sure to note this URL down, you will need it later when connecting Appwrite to DigitalOcean.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft2mgpesg059dynbq9lc3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft2mgpesg059dynbq9lc3.png" alt="Space information" width="800" height="216"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Last but not least, you need to get a set of access keys to allow Appwrite to read and write from your newly created space. To do that, visit the &lt;code&gt;API&lt;/code&gt; page from the navigation on the left, and under the &lt;code&gt;Spaces access keys&lt;/code&gt; section we click on &lt;code&gt;Generate New Key&lt;/code&gt;. Give the key a name (no worries, this name is only visible to you), and click on the blue tick icon to finish the key creation process. You will be presented with two keys. The one that is on the same line as your key name is called &lt;code&gt;Access key&lt;/code&gt;, and the one below is &lt;code&gt;Secret key&lt;/code&gt;. Please note both of them down, but be aware, the secret key is a really sensitive piece of information and DigitalOcean won’t show it to you in future. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0zxp79lml8vjeggb87js.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0zxp79lml8vjeggb87js.png" alt="Space access keys" width="800" height="424"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That’s it! You have successfully set up our DigitalOcean Space, and have all the required credentials in your hands. Let’s look at how easy it is to connect Appwrite to our newly created space.&lt;/p&gt;

&lt;h2&gt;
  
  
  🧰 Connecting Appwrite to DigitalOcean Spaces
&lt;/h2&gt;

&lt;p&gt;Before you start, make sure that you have the Appwrite instance up and running. Installation of Appwrite is as simple as running one command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker run -it --rm \
    --volume /var/run/docker.sock:/var/run/docker.sock \
    --volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
    --entrypoint="install" \
    appwrite/appwrite:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;To learn more about the installation process, you can check out our &lt;a href="https://appwrite.io/docs/installation"&gt;installation guide&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Once our Appwrite instance is up and running, we can configure the storage adapter. To do that, let’s enter the &lt;code&gt;.env&lt;/code&gt; file and locate &lt;code&gt;_APP_STORAGE_DEVICE&lt;/code&gt;. Currently, it’s set to &lt;code&gt;Local&lt;/code&gt;, but since we want to use DigitalOcean Spaces, we change it to &lt;code&gt;DOSpaces&lt;/code&gt;. With the new storage provider, we are now required to add new variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;_APP_STORAGE_DEVICE=DOSpaces
_APP_STORAGE_DO_SPACES_BUCKET=YOUT_BUCKET
_APP_STORAGE_DO_SPACES_REGION=YOUR_REGION
_APP_STORAGE_DO_SPACES_SECRET=YOUR_ACCESS_SECRET
_APP_STORAGE_DO_SPACES_ACCESS_KEY=YOUR_ACCESS_KEY
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Where do you get this information from? 😨 I have a neat trick for you! First of all, you can already fill in access and secret keys, these are the ones you got from earlier steps when creating API key on DigitalOcean. To get the bucket and region, you need to take a look at the space endpoint that we got after creating the DigitalOcean Space. For instance, my endpoint was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://project-x-bucket.fra1.digitaloceanspaces.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is a simple trick that is pretty easy to follow to get our bucket name and region. From my URL, the bucket name is &lt;code&gt;project-x-bucket&lt;/code&gt;, and the region is &lt;code&gt;fra1&lt;/code&gt;. Follow the same logic to extract information from your endpoint.&lt;/p&gt;

&lt;p&gt;Once you have all of these environment variables configured, save the file and restart appwrite using &lt;code&gt;docker-compose up -d&lt;/code&gt;. Did you notice that Docker is smart enough to only restart containers that use variables you just changed? 🤯&lt;/p&gt;

&lt;p&gt;Once the Appwrite instance is restarted, create a new account and a new project. In the left menu, select ‘Storage’ and create a new bucket. Finally, upload a file into our bucket.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa9x486xd9tgaisqqt7o4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa9x486xd9tgaisqqt7o4.png" alt="Appwrite file" width="800" height="424"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can see the file was successfully uploaded, and can start using Appwrite just like usual! To confirm you are actually talking to DigitalOcean Spaces, visit your storage and you'll see the file in there.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F77ebpg0eu62hz8zipe0k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F77ebpg0eu62hz8zipe0k.png" alt="DO Spaces file" width="800" height="403"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;Congrats! You successfully connected Appwrite to DigitalOcean Spaces 🥳 If you plan to use files directly from your Space, there are a few things to keep in mind:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Every file is compressed using GZIP compression before storing it in your Space. You will need to decompress the file after download if you plan to use it.&lt;/li&gt;
&lt;li&gt;By default, encryption is enabled on newly created buckets in Appwrite Storage. This means you won’t be able to view the file without decrypting it first. I recommend you disable encryption if you plan on downloading the files from your Space.&lt;/li&gt;
&lt;li&gt;All compression, encryption and antivirus are skipped for files above 20MB. These files are stored in your Space as plain files.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  👨‍🎓 Conclusion
&lt;/h2&gt;

&lt;p&gt;The worst nightmare for every project is bad scalability of their application. Thanks to the newly released provider system for Appwrite Storage service, you can now connect Appwrite with external storage providers instead of storing the files on your system. This prevents hitting a hard drive limit, as well as lets you use your favorite provider alongside Appwrite. And as you have seen in the tutorial above, you can easily connect Appwrite with DigitalOcean Spaces in just a few steps!&lt;/p&gt;

&lt;p&gt;If you have a project to share, need help or simply want to become a part of the Appwrite community, I would love for you to join our official Appwrite &lt;a href="https://appwrite.io/discord"&gt;Discord server&lt;/a&gt;. I can’t wait to see what you build!&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;📚&lt;/strong&gt; Learn more
&lt;/h2&gt;

&lt;p&gt;You can use the following resources to learn more and get help:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🚀 &lt;a href="https://github.com/appwrite"&gt;Appwrite Github&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📜 &lt;a href="https://appwrite.io/docs"&gt;Appwrite Docs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;💬 &lt;a href="https://appwrite.io/discord"&gt;Discord Community&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>cloud</category>
      <category>tutorial</category>
      <category>showdev</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
