<?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>Forem: Enes Akar</title>
    <description>The latest articles on Forem by Enes Akar (@enesakar).</description>
    <link>https://forem.com/enesakar</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%2F354155%2F2cb0f92c-e6cf-40d0-b213-789d67d10f4d.jpg</url>
      <title>Forem: Enes Akar</title>
      <link>https://forem.com/enesakar</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/enesakar"/>
    <language>en</language>
    <item>
      <title>Best Databases for Next.js</title>
      <dc:creator>Enes Akar</dc:creator>
      <pubDate>Thu, 03 Feb 2022 22:46:03 +0000</pubDate>
      <link>https://forem.com/enesakar/best-databases-for-nextjs-3e42</link>
      <guid>https://forem.com/enesakar/best-databases-for-nextjs-3e42</guid>
      <description>&lt;p&gt;Next.js enables developers to build full stack applications with the Server Side Rendering capability. Vercel and Netlify help them to write the backend API with serverless functions. So the next question is what is the ideal database for Next.js apps. In this post, I will review the databases which are popular among the Next.js community. I will comment on how they fit the serverless model. Two things to check:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Serverless pricing: Is the price pay-per-use? When the database is not in use, do you still have to pay?&lt;/li&gt;
&lt;li&gt;Serverless connections: Is there support for HTTP based connections? So there should not be any connection problems which are common with serverless functions.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  TLDR
&lt;/h2&gt;

&lt;p&gt;I have reviewed PlanetScale, Supabase, Upstash, Fauna, DynamoDB and MongoDB Atlas. Check the table below to compare the feature set of each product.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;PlanetScale&lt;/th&gt;
&lt;th&gt;Supabase&lt;/th&gt;
&lt;th&gt;Upstash&lt;/th&gt;
&lt;th&gt;Fauna&lt;/th&gt;
&lt;th&gt;DynamoDB&lt;/th&gt;
&lt;th&gt;MongoDB Atlas&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;API&lt;/td&gt;
&lt;td&gt;MySQL&lt;/td&gt;
&lt;td&gt;PostgreSQL&lt;/td&gt;
&lt;td&gt;Redis&lt;/td&gt;
&lt;td&gt;Fauna&lt;/td&gt;
&lt;td&gt;DynamoDB&lt;/td&gt;
&lt;td&gt;MongoDB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Type&lt;/td&gt;
&lt;td&gt;Relational&lt;/td&gt;
&lt;td&gt;Relational&lt;/td&gt;
&lt;td&gt;KV Store&lt;/td&gt;
&lt;td&gt;Document&lt;/td&gt;
&lt;td&gt;Document&lt;/td&gt;
&lt;td&gt;Document&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Free Tier&lt;/td&gt;
&lt;td&gt;🟢&lt;/td&gt;
&lt;td&gt;🟢&lt;/td&gt;
&lt;td&gt;🟢&lt;/td&gt;
&lt;td&gt;🟢&lt;/td&gt;
&lt;td&gt;🟢&lt;/td&gt;
&lt;td&gt;🟢&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Serverless Pricing&lt;/td&gt;
&lt;td&gt;🟡 (min $29)&lt;/td&gt;
&lt;td&gt;🟡 (min $25)&lt;/td&gt;
&lt;td&gt;🟢&lt;/td&gt;
&lt;td&gt;🟡 (min $22.5)&lt;/td&gt;
&lt;td&gt;🟢&lt;/td&gt;
&lt;td&gt;🟢&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;REST API&lt;/td&gt;
&lt;td&gt;🔴&lt;/td&gt;
&lt;td&gt;🟢&lt;/td&gt;
&lt;td&gt;🟢&lt;/td&gt;
&lt;td&gt;🟢&lt;/td&gt;
&lt;td&gt;🟢&lt;/td&gt;
&lt;td&gt;🟢&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GraphQL API&lt;/td&gt;
&lt;td&gt;🔴&lt;/td&gt;
&lt;td&gt;🔴&lt;/td&gt;
&lt;td&gt;🔴&lt;/td&gt;
&lt;td&gt;🟢&lt;/td&gt;
&lt;td&gt;🔴&lt;/td&gt;
&lt;td&gt;🔴&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Global replication&lt;/td&gt;
&lt;td&gt;🔴&lt;/td&gt;
&lt;td&gt;🔴&lt;/td&gt;
&lt;td&gt;🟢&lt;/td&gt;
&lt;td&gt;🟢&lt;/td&gt;
&lt;td&gt;🟢&lt;/td&gt;
&lt;td&gt;🟡  (only for Dedicated)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database Branching&lt;/td&gt;
&lt;td&gt;🟢&lt;/td&gt;
&lt;td&gt;🔴&lt;/td&gt;
&lt;td&gt;🔴&lt;/td&gt;
&lt;td&gt;🔴&lt;/td&gt;
&lt;td&gt;🔴&lt;/td&gt;
&lt;td&gt;🔴&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Realtime Updates&lt;/td&gt;
&lt;td&gt;🔴&lt;/td&gt;
&lt;td&gt;🟢&lt;/td&gt;
&lt;td&gt;🔴&lt;/td&gt;
&lt;td&gt;🔴&lt;/td&gt;
&lt;td&gt;🟢&lt;/td&gt;
&lt;td&gt;🟡 (via Realm)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Price Reads&lt;/td&gt;
&lt;td&gt;$1.5 / 10m&lt;/td&gt;
&lt;td&gt;$0.09 / GB&lt;/td&gt;
&lt;td&gt;$0.2 / 100K&lt;/td&gt;
&lt;td&gt;$0.45 / 1m (4KB)&lt;/td&gt;
&lt;td&gt;$0.25 / 1m (2KB)&lt;/td&gt;
&lt;td&gt;$0.3 / 1m (4KB)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Price Writes&lt;/td&gt;
&lt;td&gt;$1.5 / 1m&lt;/td&gt;
&lt;td&gt;$0.09 / GB&lt;/td&gt;
&lt;td&gt;$0.2 / 100K&lt;/td&gt;
&lt;td&gt;$2.25 / 1m (1KB)&lt;/td&gt;
&lt;td&gt;$1.25 / 1m (1KB)&lt;/td&gt;
&lt;td&gt;$1.25 / 1m (1KB)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Price Storage&lt;/td&gt;
&lt;td&gt;$1.25 / GB&lt;/td&gt;
&lt;td&gt;$0.125 / GB&lt;/td&gt;
&lt;td&gt;$0.25 / GB&lt;/td&gt;
&lt;td&gt;$0.23 / GB&lt;/td&gt;
&lt;td&gt;$0.25 / GB&lt;/td&gt;
&lt;td&gt;$0.25 / GB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  PlanetScale
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://planetscale.com/" rel="noopener noreferrer"&gt;PlanetScale&lt;/a&gt; is a MySQL compatible database as a service.&lt;/li&gt;
&lt;li&gt;It is uses &lt;a href="https://vitess.io/" rel="noopener noreferrer"&gt;Vitess&lt;/a&gt; at the background. This is great as Vitess is being actively used by giants like Youtube.&lt;/li&gt;
&lt;li&gt;PlanetScale introduced the &lt;a href="https://docs.planetscale.com/concepts/branching" rel="noopener noreferrer"&gt;database branching&lt;/a&gt; concept. You can create branches and merge schema changes to your production database just like git. Afaik, this feature is unique for PlanetScale.&lt;/li&gt;
&lt;li&gt;It has a free tier with 100m reads per month.&lt;/li&gt;
&lt;li&gt;Paid plans start from $29. The main price components for the paid plan are:

&lt;ul&gt;
&lt;li&gt;Storage/month: $1.25/GB after first 25GB&lt;/li&gt;
&lt;li&gt;Reads/month: $1.50 per 10m, after first 500m&lt;/li&gt;
&lt;li&gt;Writes/month: $1.50 per 1m, after first 50m&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;PlanetScale does not have an HTTP/REST based API.&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%2Fblog.upstash.com%2Fimg%2Fblog%2Fnextjsdbs%2Fn1.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%2Fblog.upstash.com%2Fimg%2Fblog%2Fnextjsdbs%2Fn1.png" alt="db"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  My Comments:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;In the home page, it is said that: Trusted By Github, Square, Slack etc. Do these companies use PlanetScale or Vitess? If these companies use PlanetScale then it is great. Otherwise, I do not feel good about this type of marketing. &lt;code&gt;Trusting Vitess&lt;/code&gt; and &lt;code&gt;Trusting PlanetScale&lt;/code&gt; are different things&lt;/li&gt;
&lt;li&gt;PlanetScale requires you to use TCP based connections (MySQL connections). This might be a problem for serverless functions as they are stateless. They say the number of connections is not a problem for Vitess but still there is a max connection limit for the plans.&lt;/li&gt;
&lt;li&gt;Lack of the HTTP/REST API makes it impossible to use PlanetScale from Cloudflare Workers and Vercel Edge platforms because they do not allow TCP connections.&lt;/li&gt;
&lt;li&gt;Planetscale paid plans start from $29, so the price does not scale to zero. Even if you are not using a DB, you have to use $29 per db at least. I think this is a conflict with the definition of &lt;code&gt;serverless&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Useful Links:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.planetscale.com/concepts/billing#planetscale-plans" rel="noopener noreferrer"&gt;Detailed pricing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://planetscale.com/blog/how-to-setup-next-js-with-prisma-and-planetscale" rel="noopener noreferrer"&gt;Tutorial using Planetscale with Next.js and Prisma&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=YqBG5rFP4Ic" rel="noopener noreferrer"&gt;Video tutorial which uses Planetscale and Next.js, then deploy to Vercel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://news.ycombinator.com/item?id=27197873" rel="noopener noreferrer"&gt;Hackernews comments on PlaneScale launch&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Supabase
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Supabase is a collection of developer tools including database, authentication service, storage. They call themselves as &lt;code&gt;Open Source Firebase Alternative&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Supabase provides a PostgreSQL database.&lt;/li&gt;
&lt;li&gt;Just like Firebase, Supabase enables developers to listen to the changes on the database in real time.&lt;/li&gt;
&lt;li&gt;Supabase provides a RESTful API using PostgREST. This is useful especially if you plan to use Supabase in serverless functions.&lt;/li&gt;
&lt;li&gt;Supabase can be used in Cloudflare Workers thanks to its REST API. Check &lt;a href="https://github.com/supabase/supabase/tree/master/examples/with-cloudflare-workers" rel="noopener noreferrer"&gt;this.&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Supabase free tier is limited to 500MB storage and 2GB data transfer monthly.&lt;/li&gt;
&lt;li&gt;Similar to PlanetScale, Supabase has a minimum cost for paid databases which is $25/month. Summary of payg pricing:

&lt;ul&gt;
&lt;li&gt;Storage: $0.125 per GB-month&lt;/li&gt;
&lt;li&gt;Data transfer: $0.09 per GB&lt;/li&gt;
&lt;/ul&gt;


&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%2Fblog.upstash.com%2Fimg%2Fblog%2Fnextjsdbs%2Fn2.jpeg" 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%2Fblog.upstash.com%2Fimg%2Fblog%2Fnextjsdbs%2Fn2.jpeg" alt="db"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  My Comments:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Supabase provides database, authentication service, object storage and also &lt;code&gt;serverless functions&lt;/code&gt; are coming. I am worried that it may be hard for a startup to provide many services with high quality and compete with Firebase backed by Google. I hope they will be successful.&lt;/li&gt;
&lt;li&gt;Just like PlanetScale, I think their pricing is not true serverless. Their paid plans require a fixed monthly cost so it never scales to zero.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Useful Links:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://supabase.com/pricing" rel="noopener noreferrer"&gt;Detailed pricing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://supabase.com/docs/guides/with-nextjs" rel="noopener noreferrer"&gt;Get started with Next.js&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=7uKQBl9uZ00" rel="noopener noreferrer"&gt;Video: Supabase Crash Course&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=p561ogKZ63o" rel="noopener noreferrer"&gt;Video tutorial: Supabase with Next.js&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Upstash Redis
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Upstash provides Redis as a service with per request pricing.&lt;/li&gt;
&lt;li&gt;It provides both Redis and REST API. So you can use it with both serverless and edge (Cloudflare Workers) functions.&lt;/li&gt;
&lt;li&gt;Upstash has regional and global options. Global database replicates data to multiple regions.&lt;/li&gt;
&lt;li&gt;It has a free tier with max 10.000 commands per day. Its pay-as-you-pricing is as follows:

&lt;ul&gt;
&lt;li&gt;Commands: $0.2 per 100K commands&lt;/li&gt;
&lt;li&gt;Storage: $0.25 per GB&lt;/li&gt;
&lt;/ul&gt;


&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%2Fblog.upstash.com%2Fimg%2Fblog%2Fnextjsdbs%2Fn3.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%2Fblog.upstash.com%2Fimg%2Fblog%2Fnextjsdbs%2Fn3.png" alt="db"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  My Comments:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Both we (Upstash) and &lt;a href="https://redis.com/" rel="noopener noreferrer"&gt;RedisLabs&lt;/a&gt; think that Redis is powerful enough for many use cases in addition to classic Redis use cases (caching, kv-store). But still I see many developers prefer SQL databases as the primary data store. If you need complex query capabilities, Redis may not be your first choice.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Useful Links:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.upstash.com/redis/overall/pricing" rel="noopener noreferrer"&gt;Detailed pricing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.upstash.com/redis/tutorials/nextjs_with_redis" rel="noopener noreferrer"&gt;Get started with Next.js and Redis&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.upstash.com/getstarted-nextjs-edge-with-redis" rel="noopener noreferrer"&gt;Getting started with Next.js Edge Functions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=FytxaSVQROc" rel="noopener noreferrer"&gt;Video: Using Serverless Redis with Next.js&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Fauna
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Fauna provides a Globally Replicated Strongly Consistent database as a cloud service.&lt;/li&gt;
&lt;li&gt;It provides GraphQL API in addition to Fauna API.&lt;/li&gt;
&lt;li&gt;Its connections are HTTP based so you can use Fauna in serverless functions without any connection related problems.&lt;/li&gt;
&lt;li&gt;Its most basic plan (Individual) starts from $22.5/month.&lt;/li&gt;
&lt;li&gt;Fauna pricing:

&lt;ul&gt;
&lt;li&gt;Reads: $0.45/Million units (4KB)&lt;/li&gt;
&lt;li&gt;Writes: $2.25/Million units (1KB)&lt;/li&gt;
&lt;li&gt;Storage: $0.23/GB&lt;/li&gt;
&lt;/ul&gt;


&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%2Fblog.upstash.com%2Fimg%2Fblog%2Fnextjsdbs%2Fn4.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%2Fblog.upstash.com%2Fimg%2Fblog%2Fnextjsdbs%2Fn4.png" alt="db"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  My Comments:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Global strong consistency comes with extra performance costs. Writes have higher latencies than other alternative databases.&lt;/li&gt;
&lt;li&gt;If you will use GraphQL then it’s great but otherwise FQL was hard for me to learn and use.&lt;/li&gt;
&lt;li&gt;Fauna pricing has its own units (TRO, TWO, TCO) and many dimensions. You need to read articles to understand the pricing.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Useful Links:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://fauna.com/pricing" rel="noopener noreferrer"&gt;Pricing Page&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://fauna.com/blog/using-next-js-with-fauna-and-graphql" rel="noopener noreferrer"&gt;Using Next.js with Fauna&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=cCD62m6yIXk" rel="noopener noreferrer"&gt;Video: Using Next.js with Fauna&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  MongoDB Atlas
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Atlas is a MongoDB as a managed service which has serverless, shared and dedicated plans..&lt;/li&gt;
&lt;li&gt;Data (REST) API has been recently &lt;a href="https://docs.atlas.mongodb.com/api/data-api/" rel="noopener noreferrer"&gt;announced&lt;/a&gt;. This allows you to access your database using HTTP.&lt;/li&gt;
&lt;li&gt;MongoDB Atlas has a Serverless plan with &lt;a href="https://docs.atlas.mongodb.com/reference/serverless-instance-limitations/" rel="noopener noreferrer"&gt;some limitations.&lt;/a&gt; The pricing of this plan is per operations so it scales to zero as expected from a serverless product.&lt;/li&gt;
&lt;li&gt;Multi region replication is only available with Dedicated plan.&lt;/li&gt;
&lt;li&gt;Serverless Pricing:

&lt;ul&gt;
&lt;li&gt;Reads: $0.30 per million unit (4KB)&lt;/li&gt;
&lt;li&gt;Writes: $1.25 per million unit (1KB)&lt;/li&gt;
&lt;li&gt;Storage: $0.25/GB-month&lt;/li&gt;
&lt;/ul&gt;


&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%2Fblog.upstash.com%2Fimg%2Fblog%2Fnextjsdbs%2Fn5.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%2Fblog.upstash.com%2Fimg%2Fblog%2Fnextjsdbs%2Fn5.png" alt="db"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  My Comments:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Serverless Plan and Data API tells us that MongoDB Atlas does not ignore the Serverless space. But both solutions are in the preview phase and needs some improvements to be a data solution ideal for serverless applications.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Useful Links:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.mongodb.com/pricing" rel="noopener noreferrer"&gt;Pricing Page&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.mongodb.com/developer/how-to/nextjs-with-mongodb/" rel="noopener noreferrer"&gt;How to Integrate MongoDB into Next.js App&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=aAupumVpqcE" rel="noopener noreferrer"&gt;Video: Integrating MongoDB Into Your NextJS App&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  DynamoDB
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Fully managed NoSQL database with key-value and document data model.&lt;/li&gt;
&lt;li&gt;The connections are HTTP based so it fits to serverless applications.&lt;/li&gt;
&lt;li&gt;Pricing is per operation in addition to data transfer cost. So it scales to zero.&lt;/li&gt;
&lt;li&gt;By default, the reads are eventually consistent. You can also request strongly consistent reads but it has some &lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.ReadConsistency.html" rel="noopener noreferrer"&gt;limitations&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;You can enable multi region replication with extra cost.&lt;/li&gt;
&lt;li&gt;The pricing:

&lt;ul&gt;
&lt;li&gt;Reads: $0.25 per million read request units (2KB)&lt;/li&gt;
&lt;li&gt;Writes: $1.25 per million read request units (1KB)&lt;/li&gt;
&lt;li&gt;Data transfer (out): $0.09 per GB&lt;/li&gt;
&lt;li&gt;Storage: $0.25 per GB-month&lt;/li&gt;
&lt;/ul&gt;


&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%2Fblog.upstash.com%2Fimg%2Fblog%2Fnextjsdbs%2Fn6.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%2Fblog.upstash.com%2Fimg%2Fblog%2Fnextjsdbs%2Fn6.png" alt="db"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  My Comments:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;For me, DynamoDB API is not as intuitive as Redis or MongoDB.&lt;/li&gt;
&lt;li&gt;There is a vendor lock-in problem. DynamoDB is only at AWS and you can not move your application to another provider without changing the code.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Useful Links:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/dynamodb/pricing/on-demand/" rel="noopener noreferrer"&gt;Pricing Page&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/leerob/nextjs-aws-dynamodb" rel="noopener noreferrer"&gt;Example App: Next.js with DynamoDB &lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://egghead.io/courses/using-dynamodb-with-next-js-b40c" rel="noopener noreferrer"&gt;Video Course: Next.js with DynamoDB&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;So what is the best database for Next.js? As expected, there is no single answer. But let's give some conditional answers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If you need a relational DB 👉🏻 Supabase or PlanetScale&lt;/li&gt;
&lt;li&gt;If you need database branching 👉🏻 PlanetScale&lt;/li&gt;
&lt;li&gt;If you need real time updates 👉🏻 Supabase or MongoDB Atlas&lt;/li&gt;
&lt;li&gt;If you need global low latency 👉🏻 Upstash or DynamoDB&lt;/li&gt;
&lt;li&gt;If you need global strong consistency 👉🏻 Fauna&lt;/li&gt;
&lt;li&gt;If you need low latency key value store 👉🏻 Upstash &lt;/li&gt;
&lt;li&gt;If you love PostgreSQL 👉🏻 Supabase&lt;/li&gt;
&lt;li&gt;If you love MySQL 👉🏻 PlanetScale&lt;/li&gt;
&lt;li&gt;If you love Redis 👉🏻 Upstash&lt;/li&gt;
&lt;li&gt;If you love Mongo 👉🏻 MongoDB Atlas&lt;/li&gt;
&lt;li&gt;If you need GraphQL 👉🏻 Fauna&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I tried to be fair and correct. If you think there is something I need to fix, please drop your comments to my &lt;a href="https://twitter.com/enesakar" rel="noopener noreferrer"&gt;twitter&lt;/a&gt;&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>database</category>
      <category>serverless</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Use Serverless Kafka as an Event Source For AWS Lambda</title>
      <dc:creator>Enes Akar</dc:creator>
      <pubDate>Mon, 24 Jan 2022 22:46:14 +0000</pubDate>
      <link>https://forem.com/enesakar/use-serverless-kafka-as-an-event-source-for-aws-lambda-1gi2</link>
      <guid>https://forem.com/enesakar/use-serverless-kafka-as-an-event-source-for-aws-lambda-1gi2</guid>
      <description>&lt;p&gt;In this tutorial we will implement a serverless message processing pipeline using Upstash Kafka and AWS Lambda. We will use Upstash Kafka as a source for an AWS Lambda function. The produced messages will trigger AWS Lambda, so your Lambda function will process the messages.&lt;/p&gt;

&lt;p&gt;Because Upstash Kafka is a true serverless product, the whole pipeline will be serverless. You pay only when your pipeline is actively processing messages.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create Upstash Kafka
&lt;/h3&gt;

&lt;p&gt;First, create an Upstash Kafka cluster and topic following &lt;a href="https://docs.upstash.com/kafka"&gt;those steps.&lt;/a&gt; You will need the endpoint, username and password in the following steps.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3pcH0NeD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://docs.upstash.com/img/kafka/howto/awslambda/cluster_detail.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3pcH0NeD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://docs.upstash.com/img/kafka/howto/awslambda/cluster_detail.png" width="880" height="280"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  Create AWS Lambda Function
&lt;/h3&gt;

&lt;p&gt;Now let’s create an AWS Lambda function. For the best performance, select the same region with Upstash Kafka cluster. We will use Node.js runtime.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--chn-RWee--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://docs.upstash.com/img/kafka/howto/awslambda/create_lambda.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--chn-RWee--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://docs.upstash.com/img/kafka/howto/awslambda/create_lambda.png" width="880" height="844"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;:::note &lt;br&gt;
You can use Serverless Framework or AWS SAM for this step.&lt;br&gt;
:::&lt;/p&gt;
&lt;h3&gt;
  
  
  Lambda Function Code
&lt;/h3&gt;

&lt;p&gt;Update your function's code as below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;
&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&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="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;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;records&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;response&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;no kafka event&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;for&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;messages&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;values&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;records&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;for&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;msg&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;)&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;buff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;'&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;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;buff&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ascii&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="c1"&gt;// process the message&lt;/span&gt;
            &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&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="na"&gt;response&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;success&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;The above code parses the Kafka message from the event parameter. AWS encodes the message using &lt;code&gt;base64&lt;/code&gt; so we decode the message and log it to the console.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create AWS Secret
&lt;/h3&gt;

&lt;p&gt;AWS Lambda trigger needs the Kafka credentials to be bundled as a secret. So we will create a secret in &lt;a href="https://console.aws.amazon.com/secretsmanager/home?region=us-east-1#!/newSecret?step=selectSecret"&gt;AWS Secrets Manager&lt;/a&gt;. Select &lt;code&gt;Other type of secret&lt;/code&gt;. Enter your Kafka cluster's username and password as key/value pairs as below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--k-AhPeG9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://docs.upstash.com/img/kafka/howto/awslambda/new_secret.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--k-AhPeG9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://docs.upstash.com/img/kafka/howto/awslambda/new_secret.png" width="880" height="572"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;In the next screen give a name to your secret.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--qa7HAOR9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://docs.upstash.com/img/kafka/howto/awslambda/new_secret2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qa7HAOR9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://docs.upstash.com/img/kafka/howto/awslambda/new_secret2.png" width="880" height="725"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  Edit AWS Lambda Role
&lt;/h3&gt;

&lt;p&gt;Now we need to configure the Lambda function’s role to access the secrets.&lt;/p&gt;

&lt;p&gt;On the AWS Lambda function’s page, click on &lt;code&gt;Configuration&lt;/code&gt; tab and &lt;code&gt;Permissions&lt;/code&gt;. Click to the link just below the &lt;code&gt;Role name&lt;/code&gt; label. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--tHAumTGj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://docs.upstash.com/img/kafka/howto/awslambda/lambda_config.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tHAumTGj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://docs.upstash.com/img/kafka/howto/awslambda/lambda_config.png" width="880" height="423"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;The IAM management console will be opened in a new tab. On the &lt;code&gt;Permissions&lt;/code&gt; tab click on the link which starts with &lt;code&gt;AWSLambdaBasicExecutionRole-....&lt;/code&gt; &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--IqyBvL4l--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://docs.upstash.com/img/kafka/howto/awslambda/lambda_config2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--IqyBvL4l--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://docs.upstash.com/img/kafka/howto/awslambda/lambda_config2.png" width="880" height="587"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click on the &lt;code&gt;Edit Policy&lt;/code&gt; button and add this configuration in the JSON tab:&lt;br&gt;
&lt;/p&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;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Action"&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="s2"&gt;"secretsmanager:GetSecretValue"&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;"Resource"&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="s2"&gt;"REPLACE_THE_ARN_OF_THE_SECRET"&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;You need to replace the ARN of the secret that you created in the previous step.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create the Trigger
&lt;/h3&gt;

&lt;p&gt;Go back to your Lambda functions page and click the &lt;code&gt;Add trigger&lt;/code&gt; button. Select &lt;code&gt;Apache Kafka&lt;/code&gt; from the menu and fill in the inputs.&lt;/p&gt;

&lt;p&gt;Bootstrap servers: copy/paste endpoint from Upstash console.&lt;/p&gt;

&lt;p&gt;Topic name: enter your topic’s name&lt;/p&gt;

&lt;p&gt;Click on the &lt;code&gt;Add&lt;/code&gt; button under Authentication. Select &lt;code&gt;SASL_SCRAM_256_AUTH&lt;/code&gt; and select the secret that you created in the previous step.&lt;/p&gt;

&lt;p&gt;Check the &lt;code&gt;Enable trigger&lt;/code&gt; checkbox and you can leave the remaining inputs as they are.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1HEZMfWX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://docs.upstash.com/img/kafka/howto/awslambda/trigger_config.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1HEZMfWX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://docs.upstash.com/img/kafka/howto/awslambda/trigger_config.png" width="880" height="1338"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing
&lt;/h3&gt;

&lt;p&gt;Now let’s produce messages and see if AWS Lambda is processing the messages. Copy the curl URL to produce a message from &lt;a href="https://console.upstash.com"&gt;Upstash Console&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
➜ curl https://full-mantis-14289-us1-rest-kafka.upstash.io/produce/newtopic/newmessage -u  ZnVsbC1tYW50aXMtMTQyODkkimaEsuUsiT9TGk3OFdjveYHBV9Jjzow03SnUtRQ:4-R-fmtoalXnoeu9TjQBOOL4njfSKwEsE10YvHMiW63hFljqUrrq5_yAq4TPGd9c6JbqfQ==

{
  "topic" : "newtopic",
  "partition" : 0,
  "offset" : 48,
  "timestamp" : 1639522675505
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check the cloudwatch (Lambda &amp;gt; Monitor &amp;gt; View logs in CloudWatch). You should see the messages you produced are logged by Lambda function.&lt;/p&gt;

</description>
      <category>kafka</category>
      <category>aws</category>
      <category>serverless</category>
      <category>node</category>
    </item>
    <item>
      <title>Build your own Waiting Room for your Web site with Cloudflare Workers and Serverless Redis</title>
      <dc:creator>Enes Akar</dc:creator>
      <pubDate>Tue, 02 Nov 2021 18:37:22 +0000</pubDate>
      <link>https://forem.com/enesakar/building-a-serverless-notification-api-for-your-web-application-with-redis-4hi4</link>
      <guid>https://forem.com/enesakar/building-a-serverless-notification-api-for-your-web-application-with-redis-4hi4</guid>
      <description>&lt;p&gt;In this blog post, we will implement a waiting room page for your website.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why?
&lt;/h3&gt;

&lt;p&gt;High number of visitors on your website is a good thing generally but not always. Sudden high traffic may easily overwhelm your applications which may disrupt your services completely. Waiting room is a solution that helps you to control the traffic and protect your resources during traffic surges. &lt;a href="https://www.cloudflare.com/waiting-room/"&gt;Cloudflare Waiting Room&lt;/a&gt; is a good solution but it is only available for business and enterprise accounts. Don’t worry, in this blog we will build a waiting room for any type of web site using Cloudflare Workers and Upstash Redis.&lt;/p&gt;

&lt;h3&gt;
  
  
  How?
&lt;/h3&gt;

&lt;p&gt;There are two parameters to control the waiting room.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Max session duration&lt;/strong&gt;: How long can a visitor stay idle on the web site?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Max website capacity&lt;/strong&gt;: How many visitors can the website allow at the same time?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When a visitor enters our website, we will generate a unique key (like a session key) and keep it in Redis. We will put this key to the Redis with an expiration time which is the &lt;strong&gt;max session duration&lt;/strong&gt;. Before letting a visitor into the website, we will check the dbsize of the Redis. If the size is higher than the **max website capacity, **then we will forward the visitor to the waiting room. The waiting room is a static html but refreshes every 30 seconds. That is, once every 30 seconds, the visitor can be allowed to enter the website if there is availability.&lt;/p&gt;

&lt;p&gt;The unique key that we generated also is written to the visitor requests as a cookie. So the requests of the same visitor will have the same key. This is required to check if a visitor has already a session inside the website when the capacity is full. So as a visitor you are allowed to enter the website even if the capacity is full as long as your key exists in the Redis keyspace. If you stay idle longer than the max session duration, then your key will be deleted from Redis. And if the capacity is full when you send a request you will be forwarded to the waiting room.&lt;/p&gt;

&lt;p&gt;We will implement this logic at Cloudflare Workers. We will use Upstash as a Redis store. Now let’s explain the background of these technology decisions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Cloudflare Workers?
&lt;/h3&gt;

&lt;p&gt;Waiting room implementation will intercept all requests to your website. So its performance overhead should be minimal. Cloudflare Workers leverages Cloudflare edge infrastructure, so it gives you the minimum latency globally. Also unlike AWS Lambda, it does not have a cold start problem. It is a serverless technology so scalability is not a problem too.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Upstash Redis?
&lt;/h3&gt;

&lt;p&gt;You need to check the current size of the website before allowing each new visitor. Cloudflare Workers is stateless so you need to keep this information externally. Redis is the best choice with its low latency. But Redis offerings require TCP based connections which are not supported by Cloudflare Workers. Upstash is the only Redis offering with a built in REST API. Also thanks to Global replication it gives you low latency all over the world.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step by Step Implementation
&lt;/h3&gt;

&lt;p&gt;We will implement the project step by step below. If you want to clone the project and set up the waiting room for your web site directly, you can follow the steps in the readme of &lt;a href="https://github.com/upstash/waiting-room"&gt;the source code&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;1&lt;/code&gt; Project Setup
&lt;/h4&gt;

&lt;p&gt;Create a project using wrangler.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
wrangler generate waiting-room

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

&lt;/div&gt;



&lt;p&gt;Then install the dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
npm install cookie upstash@redis

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

&lt;/div&gt;



&lt;h4&gt;
  
  
  &lt;code&gt;2&lt;/code&gt; Update wrangler.toml
&lt;/h4&gt;

&lt;p&gt;Update the type:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Set your Cloudflare account id. Check &lt;a href="https://developers.cloudflare.com/workers/get-started/guide#3-configure-the-workers-cli"&gt;this&lt;/a&gt; to find your account id.&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Add these variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[vars]
UPSTASH_REDIS_REST_TOKEN = "REPLACE_HERE"
UPSTASH_REDIS_REST_URL = "REPLACE_HERE"
TOTAL_ACTIVE_USERS = 10
SESSION_DURATION_SECONDS = 30
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You need to create a Global database from the &lt;a href="http://console.upstash.com"&gt;Upstash console&lt;/a&gt;. You just need to copy and paste the REST token and URL from the console. The Redis database should be empty initially and used by only this application.&lt;/p&gt;

&lt;p&gt;You should set TOTAL_ACTIVE_USERS and SESSION_DURATION_SECONDS depending on your own requirements.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;3&lt;/code&gt; index.js
&lt;/h4&gt;

&lt;p&gt;Index.js is the Cloudflare Workers implementation file. So we will put all the logic inside it. Copy and paste the below code into it:&lt;br&gt;
&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;parse&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;cookie&lt;/span&gt;&lt;span class="dl"&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;auth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dbsize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setex&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;@upstash/redis&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;


&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;UPSTASH_REDIS_REST_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;UPSTASH_REDIS_REST_TOKEN&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fetch&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;event&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="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;respondWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;handleRequest&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;request&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="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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&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;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&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="mi"&gt;500&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;COOKIE_NAME_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;__waiting_room_id&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;COOKIE_NAME_TIME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;__waiting_room_last_update_time&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;init&lt;/span&gt; &lt;span class="o"&gt;=&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;Authorization&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;Bearer &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;UPSTASH_REDIS_REST_TOKEN&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="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;handleRequest&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="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;pathname&lt;/span&gt; &lt;span class="p"&gt;}&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;URL&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;url&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;pathname&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&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;/favicon&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;cookie&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;parse&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="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&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="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;let&lt;/span&gt; &lt;span class="nx"&gt;userId&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;cookie&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;COOKIE_NAME_ID&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&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="nx"&gt;userId&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="nx"&gt;COOKIE_NAME_ID&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="nx"&gt;userId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;makeid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&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;res&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;dbsize&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="nx"&gt;log&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 capacity:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;res&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="c1"&gt;// there is enough capacity&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;TOTAL_ACTIVE_USERS&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;getDefaultResponse&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;cookie&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="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  &lt;span class="c1"&gt;// site capacity is full&lt;/span&gt;
            &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;obj2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="kd"&gt;get&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;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;obj2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1&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;// the user has already active session&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;getDefaultResponse&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;cookie&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="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c1"&gt;// capacity is full so the user is forwarded to waiting room&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;getWaitingRoomResponse&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="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;return&lt;/span&gt; &lt;span class="nx"&gt;fetch&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="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="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;getDefaultResponse&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;cookie&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="c1"&gt;// uncomment below to test the function with a static html content&lt;/span&gt;
    &lt;span class="c1"&gt;// const newResponse = new Response(default_html)&lt;/span&gt;
    &lt;span class="c1"&gt;// newResponse.headers.append('content-type', 'text/html;charset=UTF-8')&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;fetch&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newResponse&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;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;body&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;now&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;lastUpdate&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="nx"&gt;COOKIE_NAME_TIME&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;lastUpdate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nx"&gt;lastUpdate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;diff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;lastUpdate&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;updateInterval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;SESSION_DURATION_SECONDS&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&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;diff&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;updateInterval&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;setex&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;SESSION_DURATION_SECONDS&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;newResponse&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="nx"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Set-Cookie&lt;/span&gt;&lt;span class="dl"&gt;'&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;COOKIE_NAME_TIME&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;now&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;; path=/`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;newResponse&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="nx"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Set-Cookie&lt;/span&gt;&lt;span class="dl"&gt;'&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;COOKIE_NAME_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="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;; path=/`&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;newResponse&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="nx"&gt;getWaitingRoomResponse&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newResponse&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;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;waiting_room_html&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;newResponse&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="kd"&gt;set&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;text/html;charset=UTF-8&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;newResponse&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;makeid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;characters&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;charactersLength&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;characters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&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;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&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="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;characters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;charAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
            &lt;span class="nx"&gt;charactersLength&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;result&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;waiting_room_html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`
&amp;lt;title&amp;gt;Waiting Room&amp;lt;/title&amp;gt;
&amp;lt;meta http-equiv='refresh' content='30' /&amp;gt;

&amp;lt;style&amp;gt;*{box-sizing:border-box;margin:0;padding:0}body{line-height:1.4;font-size:1rem;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif;padding:2rem;display:grid;place-items:center;min-height:100vh}.container{width:100%;max-width:800px}p{margin-top:.5rem}&amp;lt;/style&amp;gt;

&amp;lt;div class='container'&amp;gt;
  &amp;lt;h1&amp;gt;
    &amp;lt;div&amp;gt;You are now in line.&amp;lt;/div&amp;gt;
    &amp;lt;div&amp;gt;Thanks for your patience.&amp;lt;/div&amp;gt;
  &amp;lt;/h1&amp;gt;
  &amp;lt;p&amp;gt;We are experiencing a high volume of traffic. Please sit tight and we will let you in soon. &amp;lt;/p&amp;gt;
  &amp;lt;p&amp;gt;&amp;lt;b&amp;gt;This page will automatically refresh, please do not close your browser.&amp;lt;/b&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
`&lt;/span&gt;


&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;default_html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`
&amp;lt;title&amp;gt;Waiting Room Demo&amp;lt;/title&amp;gt;

&amp;lt;style&amp;gt;*{box-sizing:border-box;margin:0;padding:0}body{line-height:1.4;font-size:1rem;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif;padding:2rem;display:grid;place-items:center;min-height:100vh}.container{width:100%;max-width:800px}p{margin-top:.5rem}&amp;lt;/style&amp;gt;

&amp;lt;div class="container"&amp;gt;
  &amp;lt;h1&amp;gt;
    &amp;lt;div&amp;gt;Waiting Room Demo&amp;lt;/div&amp;gt;
  &amp;lt;/h1&amp;gt;
    &amp;lt;p&amp;gt;
              Visit this site from a different browser, you will be forwarded to the waiting room when the capacity is full.
    &amp;lt;/p&amp;gt;
  &amp;lt;p&amp;gt;  Check &amp;lt;a href={"https://github.com/upstash/waiting-room"} style={{"color": "blue"}}&amp;gt;this project &amp;lt;/a&amp;gt; to set up a waiting room for your website.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the above code, you can edit the &lt;code&gt;waiting_room_html&lt;/code&gt; variable. It is the static html of the waiting room page.&lt;/p&gt;

&lt;p&gt;handleRequest method returns either getDefaultResponse or getWaitingRoomResponse depending on the availability of your website.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;4&lt;/code&gt; Run Locally
&lt;/h4&gt;

&lt;p&gt;To test the waiting room locally, it might make sense to set capacity to 1, session duration to 30 seconds. Then run&lt;br&gt;
&lt;/p&gt;

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

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

&lt;/div&gt;



&lt;p&gt;Now open &lt;a href="http://127.0.0.1:8787/"&gt;http://127.0.0.1:8787/&lt;/a&gt; at Chrome, you will see:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--A0VljZeR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.upstash.com/assets/images/b1-83b73594e2eb44b5af12097d3f04d2b4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--A0VljZeR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.upstash.com/assets/images/b1-83b73594e2eb44b5af12097d3f04d2b4.png" alt="alt_text" title="image_tooltip" width="880" height="616"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And then open the same url on Safari (or Chrome incognito), you will see:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Imu8vfLs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.upstash.com/assets/images/b2-a5c2f40b78a6c6ce04c9f013dcaabce8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Imu8vfLs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.upstash.com/assets/images/b2-a5c2f40b78a6c6ce04c9f013dcaabce8.png" alt="alt_text" title="image_tooltip" width="880" height="649"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Wait for longer than 30 seconds. You should see the page in waiting room will refresh and enter the website.&lt;/p&gt;

&lt;p&gt;Our local does not forward to a real website so we see the 404 page of Cloudflare, that’s ok.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;5&lt;/code&gt; Publish
&lt;/h4&gt;

&lt;p&gt;Deploy your CF Workers function with:&lt;br&gt;
&lt;/p&gt;

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

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

&lt;/div&gt;



&lt;p&gt;It will give you a url like: &lt;a href="https://waiting-room.upsdev.workers.dev/"&gt;https://waiting-room.upsdev.workers.dev/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now let’s route our worker to your website. First your domain's nameservers should point to Cloudflare. Check &lt;a href="https://developers.cloudflare.com/automatic-platform-optimization/get-started/change-nameservers"&gt;this&lt;/a&gt;. Then, you need to add your domain as a route and select your Workers function in the CF Workers dashboard.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--fdu0ODAg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.upstash.com/assets/images/b3-39a8a01e97cedff3527cca2e3c66a3d1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fdu0ODAg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.upstash.com/assets/images/b3-39a8a01e97cedff3527cca2e3c66a3d1.png" alt="alt_text" title="image_tooltip" width="880" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;We have successfully built a waiting room without touching our application code thanks to Cloudflare Workers and Upstash Redis. We believe this is another indicator of how powerful edge functions become together with Upstash.&lt;/p&gt;

&lt;p&gt;There are things to improve:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Estimated waiting time: We could calculate and show average waiting time.&lt;/li&gt;
&lt;li&gt;Fair and ordered queue: Currently waiting visitors randomly enter the site when there is availability. We might keep a queue and try to let visitors in an ordered way.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both of the above improvements require keeping more state and making more remote calls. That’s why we preferred to skip those. If your use case strictly requires those, you may check &lt;a href="https://blog.cloudflare.com/building-waiting-room-on-workers-and-durable-objects/"&gt;this blog&lt;/a&gt; to be inspired from Cloudflare team’s work on their enterprise solution.&lt;/p&gt;

&lt;p&gt;Check &lt;a href="https://github.com/upstash/waiting-room"&gt;the source code&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Let us know your thoughts on &lt;a href="https://twitter.com/upstash"&gt;Twitter&lt;/a&gt; or &lt;a href="https://discord.gg/w9SenAtbme"&gt;Discord&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>cloudflare</category>
      <category>cloudflareworkers</category>
      <category>redis</category>
    </item>
    <item>
      <title>Building a Serverless Notification API for Your Web Application with Redis</title>
      <dc:creator>Enes Akar</dc:creator>
      <pubDate>Fri, 29 Oct 2021 17:51:43 +0000</pubDate>
      <link>https://forem.com/enesakar/building-a-serverless-notification-api-for-your-web-application-with-redis-m5o</link>
      <guid>https://forem.com/enesakar/building-a-serverless-notification-api-for-your-web-application-with-redis-m5o</guid>
      <description>&lt;p&gt;Notifications and announcements help you communicate with your web site visitors. It is not feasible to update your code and redeploy your website each time you want to show a new message. It may also be too much investment to set up a backend and maintain it to just serve these notifications. In this article, we will build a website which will load the notification message directly from the Redis database without a backend.&lt;/p&gt;

&lt;h3&gt;
  
  
  Backendless? How is that possible?
&lt;/h3&gt;

&lt;p&gt;Yes, we will not use any backend service, even a serverless function. We will access Redis from the client side directly. This is possible with the read only REST API provided by Upstash.&lt;/p&gt;

&lt;h3&gt;
  
  
  Requirements
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The page will display a notification if the user has not already seen the notification before.&lt;/li&gt;
&lt;li&gt;The page will only show the latest notification.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Check out &lt;a href="https://github.com/upstash/examples/tree/master/serverless-notification-api"&gt;the code here&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;I will create a React application but you can use any other web framework. It will simply call the Redis REST API and show the message as a notification.&lt;/p&gt;

&lt;p&gt;Create the app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx create-react-app serverless-notification-api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install a toast component to show the notification:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--save&lt;/span&gt; react-toastify
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a free database from &lt;a href="https://console.upstash.com/"&gt;Upstash&lt;/a&gt; and copy the REST url and read only token. You should switch the Read-Only Token setting. In the database details page, click on the &lt;code&gt;Read-Only Token&lt;/code&gt; switch.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--n6eOcOSm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://docs.upstash.com/img/examples/restapi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--n6eOcOSm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://docs.upstash.com/img/examples/restapi.png" alt="alt_text" title="Rest Api" width="880" height="144"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Implementation
&lt;/h3&gt;

&lt;p&gt;The logic is simple. We will keep the notifications in a Redis Sorted Set. We will keep a version (integer) in the local storage. We will use the versions as scores in the sorted set. Each notification message will have a version (score) and the higher score means the newer message. At each page load, we will query the Redis sorted set to load the messages which have higher scores than the locally stored version. After loading a notification message I will set my local version equal to the latest notification’s version. This will prevent showing the same notification to the same users more than once. Here the implementation:&lt;br&gt;
&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;logo&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;./logo.svg&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./App.css&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;ToastContainer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;toast&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;react-toastify&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-toastify/dist/ReactToastify.css&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;useEffect&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;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

   &lt;span class="nx"&gt;useEffect&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;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;fetchData&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;let&lt;/span&gt; &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;notification-version&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
               &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;version&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="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;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;REPLACE_UPSTASH_REDIS_REST_URL/zrevrangebyscore/messages/+inf/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/WITHSCORES/LIMIT/0/1&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;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Bearer REPLACE_UPSTASH_REDIS_REST_TOKEN&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;res&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;response&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;parseInt&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="nx"&gt;result&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                   &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;notification-version&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;v&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="p"&gt;}&lt;/span&gt;
               &lt;span class="nx"&gt;toast&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="nx"&gt;result&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="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;e&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="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&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="nx"&gt;fetchData&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;App&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
           &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;header&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;App-header&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
               &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;img&lt;/span&gt; &lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;logo&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;App-logo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;alt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;logo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
               &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                   &lt;span class="nx"&gt;Edit&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;/&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;js&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/code&amp;gt; and save to reload&lt;/span&gt;&lt;span class="err"&gt;.
&lt;/span&gt;               &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/p&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;               &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;
                   &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;App-link&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
                   &lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://reactjs.org&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
                   &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;_blank&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
                   &lt;span class="nx"&gt;rel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;noopener noreferrer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
               &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                   &lt;span class="nx"&gt;Learn&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;
               &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/a&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;           &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/header&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;           &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ToastContainer&lt;/span&gt;&lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
       &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&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="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  How to Add New Notification Messages
&lt;/h3&gt;

&lt;p&gt;You can simply add new messages to the Redis sorted set with a highest score so it will be displayed to the user with page loads. For our application the name of the sorted set is &lt;code&gt;messages&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--LXQXPNRi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://docs.upstash.com/img/examples/notif/cli.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--LXQXPNRi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://docs.upstash.com/img/examples/notif/cli.png" alt="alt_text" title="cli" width="880" height="89"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also remove a message using the &lt;a href="https://redis.io/commands/zrem"&gt;ZREM&lt;/a&gt; command.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;You do not need a backend to access Upstash Redis thanks to the REST API. You can expose the token with your client side application, as the token only allows read-only access. This helps developers to build applications without backend for many use cases where the data is already available publicly.&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>redis</category>
      <category>node</category>
      <category>react</category>
    </item>
    <item>
      <title>Serverless API with Java and Redis</title>
      <dc:creator>Enes Akar</dc:creator>
      <pubDate>Thu, 30 Sep 2021 23:48:42 +0000</pubDate>
      <link>https://forem.com/enesakar/serverless-api-with-java-and-redis-26hh</link>
      <guid>https://forem.com/enesakar/serverless-api-with-java-and-redis-26hh</guid>
      <description>&lt;p&gt;In this tutorial, we will build a stateful serverless API using Java and Redis on AWS Lambda. The API will simply count the page views and return it as http response.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Install node and npm&lt;/li&gt;
&lt;li&gt;Install the Serverless Framework installed with an AWS account set up.&lt;/li&gt;
&lt;li&gt;Install JDK and not Java JRE. Set your JAVA_HOME.&lt;/li&gt;
&lt;li&gt;Install Apache Maven.&lt;/li&gt;
&lt;li&gt;Create a free Serverless Redis database from &lt;a href="https://console.upstash.com"&gt;Upstash&lt;/a&gt; as described &lt;a href="https://docs.upstash.com/"&gt;here&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;Create the project:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;serverless create --template aws-java-maven --name serverless-api-java-redis -p serverless-api-java-redis&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Add jedis as dependency to the pom.xml:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;redis.clients&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;jedis&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;3.6.0&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Update serverless.yml adding an http endpoint as below:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;functions:
 hello:
   handler: com.serverless.Handler
   events:
     - httpApi:
         path: /hello
         method: get
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Code
&lt;/h3&gt;

&lt;p&gt;Update Handler.java as below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;com.serverless&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.util.Map&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.amazonaws.services.lambda.runtime.Context&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.amazonaws.services.lambda.runtime.RequestHandler&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;redis.clients.jedis.Jedis&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Handler&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;RequestHandler&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;,&lt;/span&gt; &lt;span class="nc"&gt;ApiGatewayResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nd"&gt;@Override&lt;/span&gt;
  &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;ApiGatewayResponse&lt;/span&gt; &lt;span class="nf"&gt;handleRequest&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Context&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
     &lt;span class="nc"&gt;Jedis&lt;/span&gt; &lt;span class="n"&gt;jedis&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;Jedis&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"us1-lasting-lion-32618.upstash.io"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;32618&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
     &lt;span class="n"&gt;jedis&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;auth&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"d5033b46dfbgheb6718f9dbbd2925"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
     &lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jedis&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;incr&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"counter"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
     &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Hello World, Count:"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
     &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;ApiGatewayResponse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
           &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setStatusCode&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
           &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setObjectBody&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
           &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the above code, you need to replace your Redis endpoint and password. You can copy Jedis connection code from the &lt;a href="https://console.upstash.com"&gt;Upstash console&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deploy and Run
&lt;/h3&gt;

&lt;p&gt;First build your project with &lt;code&gt;mvn clean install&lt;/code&gt;. Then deploy to AWS with &lt;code&gt;serverless deploy&lt;/code&gt;. The last command will output the endpoint URL as below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
Serverless: Stack update finished...

Service Information

service: serverless-api-java-redis

stage: dev

region: us-east-1

stack: serverless-api-java-redis-dev

resources: 11

api keys:

  None

endpoints:

  GET - https://60tinzba0a.execute-api.us-east-1.amazonaws.com/hello

functions:

  hello: serverless-api-java-redis-dev-hello

layers:

  None

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

&lt;/div&gt;



</description>
      <category>serverless</category>
      <category>java</category>
      <category>redis</category>
      <category>aws</category>
    </item>
    <item>
      <title>AWS Lambda vs Cloudflare Workers</title>
      <dc:creator>Enes Akar</dc:creator>
      <pubDate>Thu, 30 Sep 2021 18:41:38 +0000</pubDate>
      <link>https://forem.com/enesakar/aws-lambda-vs-cloudflare-workers-1l3d</link>
      <guid>https://forem.com/enesakar/aws-lambda-vs-cloudflare-workers-1l3d</guid>
      <description>&lt;p&gt;In this article, I will compare AWS Lambda and Cloudflare Workers based on my experiences and observations during developing applications to showcase &lt;a href="https://upstash.com"&gt;Upstash&lt;/a&gt; use cases. &lt;/p&gt;

&lt;p&gt;Both are similar in that they provide a serverless runtime for developers to run their functions. But there are many differences between them and I will list them in the following categories: Performance, Pricing, Runtime and Language Support, Tools and Resources, Ecosystem and Integrations, Configurability and Limitations.  I will declare a winner for each category from my point of view.&lt;/p&gt;

&lt;h3&gt;
  
  
  Performance
&lt;/h3&gt;

&lt;p&gt;AWS Lambda runs in containers and at the selected runtime (e.g. Node). Cloudflare Workers run your code in V8 Isolates. Cloudflare Workers has minimal latency in runtime startups. This eliminates the famous cold start problem.&lt;/p&gt;

&lt;p&gt;In addition, because Cloudflare Workers is distributed globally on the Cloudflare network; you will experience lower latency all over the world. To minimize latency in AWS Lambda, your function and client must reside in the same region.&lt;/p&gt;

&lt;p&gt;According to &lt;a href="https://news.ycombinator.com/item?id=17445134"&gt;this benchmark&lt;/a&gt;; Cloudflare Workers looks to perform better. Note that the comparison was made by Cloudflare.&lt;/p&gt;

&lt;p&gt;In this category, Cloudflare Workers wins.&lt;/p&gt;

&lt;h3&gt;
  
  
  Runtime &amp;amp; Language Support
&lt;/h3&gt;

&lt;p&gt;AWS Lambda natively supports Java, Go, PowerShell, Node.js, C#, Python, and Ruby. Cloudflare Workers supports Javascript and TypeScript. Cloudflare Workers also supports WASM Compiled languages, but using them doesn't &lt;a href="https://developers.cloudflare.com/workers/tutorials/workers-kv-from-rust"&gt;seem very easy&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This category is a clear win for AWS Lambda.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configurability and Limitations
&lt;/h3&gt;

&lt;p&gt;With AWS Lambda, you can choose a custom runtime (Layers) and memory size from 128MB to 10GB. Cloudflare Workers memory is fixed at 128MB. AWS Lambda function can run up to 900 seconds (max timeout). This is 30 seconds for Cloudflare Workers.&lt;/p&gt;

&lt;p&gt;Cloudflare Workers are not Node-based, so packages that require Node dependencies are not supported in Cloudflare Workers. AWS Lambda has no such issue.&lt;/p&gt;

&lt;p&gt;AWS Lambda wins in this category.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pricing
&lt;/h3&gt;

&lt;p&gt;AWS request pricing:  $0.2 per 1M requests&lt;/p&gt;

&lt;p&gt;Cloudflare Workers request pricing: $0.15 per 1M requests&lt;/p&gt;

&lt;p&gt;AWS duration pricing: $16.67 per million GB/s&lt;/p&gt;

&lt;p&gt;Cloudflare Workers duration pricing: $12.50 per million GB/s&lt;/p&gt;

&lt;p&gt;In this category, Cloudflare Workers wins.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tools and Resources
&lt;/h3&gt;

&lt;p&gt;AWS Lambda was introduced in 2014. Cloudflare Workers was released in 2018. AWS Lambda is considered the pioneer of the serverless space. There are numerous third-party management and deployment tools for AWS Lambda. You'll find more tools and technical resources on AWS Lambda than Cloudflare Workers.&lt;/p&gt;

&lt;p&gt;AWS Lambda wins in this category.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ecosystem and Integrations
&lt;/h3&gt;

&lt;p&gt;You can use most AWS services with AWS Lambda. You can access AWS databases and use AWS services to trigger your function. AWS Lambda can be part of many different use cases with different components. It can be part of an API for the web service as well as a stream processing engine. Cloudflare Workers has far fewer options. Use cases are mostly limited to web applications and edge computing. You can use Cloudflare Workers with Worker KV and Durable Objects, but they provide much more limited use cases than all AWS services.&lt;/p&gt;

&lt;p&gt;AWS Lambda obviously wins this category.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
  &lt;tr&gt;
   &lt;td&gt;
&lt;strong&gt;Category&lt;/strong&gt;
   &lt;/td&gt;
   &lt;td&gt;
&lt;strong&gt;Winner&lt;/strong&gt;
   &lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;Performance
   &lt;/td&gt;
   &lt;td&gt;Cloudflare Workers
   &lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;Runtime &amp;amp; Language Support
   &lt;/td&gt;
   &lt;td&gt;AWS Lambda
   &lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;Tools and Resources
   &lt;/td&gt;
   &lt;td&gt;AWS Lambda
   &lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;Pricing
   &lt;/td&gt;
   &lt;td&gt;Cloudflare Workers
   &lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;Ecosystem and Integrations
   &lt;/td&gt;
   &lt;td&gt;AWS Lambda
   &lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt; Configurability and Limitations
   &lt;/td&gt;
   &lt;td&gt;AWS Lambda
   &lt;/td&gt;
  &lt;/tr&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  What about Lambda@Edge?
&lt;/h3&gt;

&lt;p&gt;It might make sense to compare Cloudflare Workers with Lambda@Edge rather than Lambda. But it seems to me that Lambda@Edge is more of a feature of Cloudfront than a standalone product. Other than a few blog posts and announcements, I couldn't find many resources. Maybe AWS isn't investing much in this anymore.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;In this post, I tried to compare two popular serverless products based on my experiences and observations. So if you think differently, please leave a comment here or on my &lt;a href="https://twitter.com/enesakar"&gt;twitter&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There is no clear answer to the question of which product is better. It depends on the use case and your personal preferences. While AWS Lambda seems like the safer choice for many use cases; Cloudflare Workers is an excellent choice for use cases where you need low latency globally.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Best Databases for Serverless</title>
      <dc:creator>Enes Akar</dc:creator>
      <pubDate>Tue, 28 Sep 2021 18:14:26 +0000</pubDate>
      <link>https://forem.com/enesakar/best-databases-for-serverless-582g</link>
      <guid>https://forem.com/enesakar/best-databases-for-serverless-582g</guid>
      <description>&lt;p&gt;Serverless is great for developers who want to build an application end to end with lowest maintenance and financial cost. Serverless functions enable you to run your own code without maintaining the backend infra. But they are stateless by design so you need an external data store. So the next question is which databases are best fit for serverless? The good news is database vendors see the serverless trend and they are either launching either serverless offerings or trying to adapt their product. In this article, I will first list the things that make a database great for serverless. Then I will list the database that I personally think to be candidates to be the great database for Serverless.&lt;/p&gt;

&lt;h3&gt;
  
  
  What makes a database great for Serverless?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ease of use:&lt;/strong&gt; Next generation developers do not want to spend days and weeks learning the technologies. They want to learn by watching youtube videos instead of reading pages of docs. So a database for serverless should be simple and intuitive.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Serverless Pricing:&lt;/strong&gt; The price should scale to zero. Why should I pay for something that I do not use. Pricing models based on per-bandwidth or per-request are serverless as they correlate with your exact usage. Personally I prefer per-request as it is always easier to estimate than bandwidth.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Developer friendly pricing:&lt;/strong&gt; Pricing should be simple without traps. Some pricing pages are so complicated that there are extra articles and blog posts to explain the details. Some vendors have complex calculators. Unfortunately most vendors prefer a complex pricing to minimize their risks. So it is hard to foresee how much you will pay before seeing the invoice.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Serverless Connections:&lt;/strong&gt; Serverless functions can scale to hundreds even thousands in seconds. If your database requires persistent connections then you can easily hit the connection limits of your database. HTTP based connections do not have such a problem as those are more lightweight and generally do not have such limitations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Low latency:&lt;/strong&gt; Extra latency is cost in serverless functions. But the real cost is the unhappy users waiting idle on your website.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Portability:&lt;/strong&gt; If you want to move to another cloud vendor, do you have to rewrite the data layer. Or are you able to test your application in your laptop without connecting to the remote database.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consistency:&lt;/strong&gt; Generally, there is a tradeoff between consistency and performance. The databases are at different places on the consistency &amp;amp; performance spectrum. A good database should clearly define its consistency guarantees so the user will be aware of the possible issues and risks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scalability:&lt;/strong&gt; The database should keep up when your application starts to receive high throughput. Ideally, scaling should be automatic or managed by the cloud provider. You should not worry about it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Edge friendly:&lt;/strong&gt; Cloudflare Workers and Fasty Compute are cool technologies which enable you to run your serverless functions at the edge closest to your user. Those require REST based connections but also globally replicated database to minimize the latency.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Globality:&lt;/strong&gt; As your application gets globally popular, you need a database which provides good performance all over the world. Multi region replication is the key feature to enable this. But be careful, check the pricing. This feature can be expensive.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The best databases for Serverless?
&lt;/h3&gt;

&lt;p&gt;In this section, I will share my impressions and notes from trying the following databases. They are not based on scientific analysis but I hope to give you an idea before doing any real comparison analysis.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MongoDB (Atlas)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ease of use: MongoDB is the most popular NoSQL database and document store. Its API is very simple and intuitive thanks to its JSON based data model.&lt;/li&gt;
&lt;li&gt;Portability: Almost all cloud providers have database products with MongoDB API as well as you can run MongoDB yourself on-premise. So there is no portability issue.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.mongodb.com/cloud/atlas"&gt;MongoDB&lt;/a&gt; Atlas recently announced its Serverless database type. My personal impression is that it was more like a hosted MongoDB instance under a serverless pricing model. As seen &lt;a href="https://docs.atlas.mongodb.com/reference/serverless-instance-limitations/"&gt;here&lt;/a&gt; many features are not supported for Serverless database including Node.js SDK.&lt;/li&gt;
&lt;li&gt;Serverless Connections: MongoDB Atlas does not provide you an HTTP based (e.g. REST) API. You can hit the connection limit easily. That’s why they publish articles how to avoid this &lt;a href="https://docs.atlas.mongodb.com/best-practices-connecting-from-aws-lambda/"&gt;https://docs.atlas.mongodb.com/best-practices-connecting-from-aws-lambda/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://www.mongodb.com/community/forums/t/what-optimizations-for-not-hitting-connection-limit-in-serverless-functions/11507"&gt;https://www.mongodb.com/community/forums/t/what-optimizations-for-not-hitting-connection-limit-in-serverless-functions/11507&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cassandra (Datastax Astra)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Datastax Astra provides REST and GraphQL API in addition to native Cassandra drivers.&lt;/li&gt;
&lt;li&gt;Ease of use: For me it was harder to learn Cassandra compared to MongoDB and Redis. However, Datastax team did a good job with the console, it was very intuitive. But still I remember I was lost in the docs with different versions of the REST API.&lt;/li&gt;
&lt;li&gt;Developer friendly pricing: The pricing of &lt;a href="https://www.datastax.com/products/datastax-astra"&gt;Datastax Astra&lt;/a&gt; is not a simple one. Every unit is calculated separately (reads, writes, bandwidth) and they have different prices depending on cloud provider and even region. Datastax has its own definition of read/write units (RRU and WRU).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;DynamoDB (AWS)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ease of use: If you are an AWS user, it is easy to start and work with &lt;a href="https://aws.amazon.com/dynamodb/"&gt;DynamoDB&lt;/a&gt;. However I think MongoDB has an easier and more intuitive query API.&lt;/li&gt;
&lt;li&gt;Serverless connections: DynamoDB connections are http based, you do not have any connection limit problem.&lt;/li&gt;
&lt;li&gt;Portability: Biggest problem is here. You can not use DynamoDB anywhere but AWS.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;FaunaDB&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Consistency: Probably FaunaDB is the best on this list in terms of consistency. It guarantees consistency along with global replication. But beware that this comes at extra performance costs. Consider whether you really need such a strong consistency.&lt;/li&gt;
&lt;li&gt;Performance: It has higher latency than others due to its strong consistency.&lt;/li&gt;
&lt;li&gt;Ease of use: Honestly, FQL was the hardest one for me to learn and use. But &lt;a href="https://fauna.com/"&gt;FaunaDB&lt;/a&gt; also supports GraphQL API, so it might be easier.&lt;/li&gt;
&lt;li&gt;Serverless connections: Thanks to its HTTP based connection, FaunaDB does not have problem with connection limits.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Upstash&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ease of use: Redis is probably the easiest to learn API in this list. Upstash makes it easy to use in the cloud with its intuitive and simple interface.&lt;/li&gt;
&lt;li&gt;Developer friendly pricing: &lt;a href="https://upstash.com/"&gt;Upstash&lt;/a&gt; pricing is based on per-request. It also sets price-caps, so it guarantees that you will not pay more than a predetermined amount.&lt;/li&gt;
&lt;li&gt;Performance: Thanks to memory based storage, Upstash gives the best latency numbers. Check &lt;a href="https://serverless-battleground.vercel.app/"&gt;this&lt;/a&gt; to get an idea.&lt;/li&gt;
&lt;li&gt;If you need complex query capabilities (joins etc), Redis data structures may not be practical. You may need a database with better SQL support in such a case.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Firestore&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ease of use: Firestore is one of the best in this list for this category. The UI and API is quite intuitive.&lt;/li&gt;
&lt;li&gt;Portability: Just like DynamoDB, Firestore has a vendor-lock problem. It is good as long as you are on Google Cloud.&lt;/li&gt;
&lt;li&gt;Performance: I heard some complaints about its performance before. I have personally experienced its latency is worse than others together with FaunaDB. So there is a question mark here.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Closing Words
&lt;/h3&gt;

&lt;p&gt;I believe that serverless is the future of development and there is an ongoing transition towards serverless. Serverless enables developers and small companies to create scalable products with a very limited budget. Databases should be part of this transition with offerings which are coherent with serverless principles.  &lt;/p&gt;

</description>
      <category>serverless</category>
      <category>mongodb</category>
      <category>aws</category>
      <category>redis</category>
    </item>
    <item>
      <title>5 ms Global Redis Latency with Edge Caching</title>
      <dc:creator>Enes Akar</dc:creator>
      <pubDate>Mon, 23 Aug 2021 16:54:33 +0000</pubDate>
      <link>https://forem.com/enesakar/5-ms-global-redis-latency-with-edge-caching-3d17</link>
      <guid>https://forem.com/enesakar/5-ms-global-redis-latency-with-edge-caching-3d17</guid>
      <description>&lt;p&gt;1 ms latency is easy with Redis when the database and clients are in the same region. But if you want the clients to be globally distributed then the latency rises over 100 ms. We built Edge Caching to overcome this.&lt;/p&gt;

&lt;h2&gt;
  
  
  Edge Caching
&lt;/h2&gt;

&lt;p&gt;With edge caching, REST responses are cached at edge locations all over the world, just like the CDN. We see 5ms global latency at average when edge caching is enabled. See &lt;a href="https://edge-benchmark.vercel.app/"&gt;our benchmark application&lt;/a&gt; where we record latency numbers from the clients located in 10 different regions. We compare the latency between the requests that are made to edge url and plain REST url. As the distance between the server and the client increases, the impact of edge caching increases. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://edge-benchmark.vercel.app/"&gt;Click here&lt;/a&gt; to run the test yourself.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--mTzOtFw3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f3d3nul1mvzhbcxdi7zx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mTzOtFw3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f3d3nul1mvzhbcxdi7zx.png" alt="Edge caching Architecture"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Use cases
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Web/mobile (backendless) clients
&lt;/h3&gt;

&lt;p&gt;Upstash provides read only authentication tokens to enable users to access the database without any backend. You can directly access the Redis from the web or mobile application. In this architecture, clients can be anywhere. It makes a lot of sense to cache the data in locations closest to your users.&lt;/p&gt;

&lt;h3&gt;
  
  
  Multi region serverless architectures
&lt;/h3&gt;

&lt;p&gt;You can run your AWS Lambda function in multiple regions to lower global latency. Vercel/Netlify functions run in different regions in some configurations. Serverless Redis with edge caching will give you fast data wherever your serverless functions are.&lt;/p&gt;

&lt;h3&gt;
  
  
  Edge functions
&lt;/h3&gt;

&lt;p&gt;Edge computing (Cloudflare workers etc) is becoming a popular way of building globally fast applications. The challenge with edge functions is that you do not have many options to keep your data. Redis with edge caching is a great fit for edge functions with its low latency and lightweightness.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get Started
&lt;/h2&gt;

&lt;p&gt;You can enable edge caching, in the Upstash console. Beware that edge caching has &lt;a href="https://docs.upstash.com/account/pricing#edge-caching"&gt;an extra cost.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once edge caching is enabled, you can find the Edge URL in the REST API dialog. Edge caching is available only for GET calls. For updates (POST), you can still use the plain REST API.&lt;/p&gt;

&lt;p&gt;By default, the cached responses expire in 30 seconds. You can control this with the &lt;code&gt;Cache-Control: max-age=&amp;lt;seconds&amp;gt;&lt;/code&gt; header.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl https://us1-smart-bunny-32732.edge-c.upstash.io/get/foo &lt;span class="se"&gt;\&lt;/span&gt;

&lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer 2dfgf98elrg0w009c842z2adfdde9132"&lt;/span&gt;

&lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Cache-Control: max-age=50"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first request to the above URL will fetch the response from the origin. The next requests will be fetched from edge locations. The cached responses will expire after 50 seconds at each edge location.&lt;/p&gt;

&lt;h2&gt;
  
  
  Edge Caching vs Global Database
&lt;/h2&gt;

&lt;p&gt;Building a global database by replicating Redis to multiple regions is in our roadmap. Global databases will give you better consistency guarantees and write latency by replicating the writes too. But it can be very costly to replicate the database to all regions. Still you may need caching to minimize latency in all locations. So Edge caching and Global replication are solutions that complete each other rather than compete. If your use case tolerates eventually consistent reads, edge caching is already a great solution which provides global fast data.&lt;/p&gt;

&lt;p&gt;Looking forward to hearing from you about our efforts on serverless and edge data. Talk to us on &lt;a href="https://twitter.com/upstash"&gt;twitter&lt;/a&gt; and &lt;a href="https://discord.com/invite/w9SenAtbme"&gt;discord&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>redis</category>
      <category>edge</category>
      <category>aws</category>
    </item>
    <item>
      <title>The Most Minimalist Next.js TODO app</title>
      <dc:creator>Enes Akar</dc:creator>
      <pubDate>Wed, 14 Jul 2021 08:06:09 +0000</pubDate>
      <link>https://forem.com/enesakar/the-most-minimalist-next-js-todo-app-4dk</link>
      <guid>https://forem.com/enesakar/the-most-minimalist-next-js-todo-app-4dk</guid>
      <description>&lt;p&gt;In this article, we will build a Serverless Next.js based TODO application. We will try our best to make it minimalist. It will not have any database connection. It will not have any extra dependency other than Next.js. It will not have any buttons. Besides, &lt;code&gt;minimalism&lt;/code&gt; is cool and clean, I love it because I am a lazy developer :)&lt;/p&gt;

&lt;h3&gt;
  
  
  Why do we avoid database connections?
&lt;/h3&gt;

&lt;p&gt;Next.js is a modern framework which enables the front-end developers to develop full stack applications. Serverless functions have an important role in simplifying backend development for Next.js developers. As you probably know, serverless functions do not like database connections due to their stateless nature. See &lt;a href="https://stackoverflow.com/questions/63208960/vercel-creates-new-db-connection-for-every-request"&gt;here&lt;/a&gt; and &lt;a href="https://blog.upstash.com/serverless-database-connections"&gt;here&lt;/a&gt; as examples of problems of database connections inside serverless functions.&lt;/p&gt;

&lt;h3&gt;
  
  
  REST is an answer
&lt;/h3&gt;

&lt;p&gt;REST allows client and server to communicate with no session information. This statelessness and its simple nature makes REST a perfect communication protocol for serverless environments. We will access &lt;a href="https://upstash.com/"&gt;Upstash Redis&lt;/a&gt; with REST.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Project Stack
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Frontend: &lt;a href="https://nextjs.org/"&gt;Next.js&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Backend: &lt;a href="https://vercel.com/docs/serverless-functions/introduction"&gt;Vercel functions&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Database: &lt;a href="https://upstash.com"&gt;Upstash Redis&lt;/a&gt; with &lt;a href="https://docs.upstash.com/features/restapi"&gt;REST API&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;See the demo: &lt;a href="https://nextjs-todo-zeta.vercel.app/"&gt;https://nextjs-todo-zeta.vercel.app/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;See the code: &lt;a href="https://github.com/upstash/examples/tree/master/nextjs-todo"&gt;https://github.com/upstash/examples/tree/master/nextjs-todo&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Create a Next.js app: &lt;code&gt;npx create-next-app&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Create an &lt;a href="https://console.upstash.com"&gt;Upstash Redis database&lt;/a&gt; in AWS-US-EAST-1 region and copy the REST URL and token.&lt;/p&gt;

&lt;p&gt;The project will be a single page application with 3 API endpoints:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;pages/api/list.js: Lists the TODO items.&lt;/li&gt;
&lt;li&gt;pages/api/add.js: Adds a TODO item.&lt;/li&gt;
&lt;li&gt;pages/api/remove.js: Removes a TODO item.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Code
&lt;/h3&gt;

&lt;p&gt;Add pages/api/list.js as below:&lt;br&gt;
&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;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&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="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;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;REPLACE_YOUR_TOKEN&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;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://REPLACE_YOUR_ENDPOINT/lrange/todo/0/100?_token=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;token&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;fetch&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;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&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="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&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;let&lt;/span&gt; &lt;span class="nx"&gt;result&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="nx"&gt;stringify&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;result&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="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&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="nx"&gt;result&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;Add pages/api/add.js as below:&lt;br&gt;
&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;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&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="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;req&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="nx"&gt;todo&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="nx"&gt;status&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;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;todo parameter required.&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;let&lt;/span&gt; &lt;span class="nx"&gt;todo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;encodeURI&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;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;todo&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;REPLACE_YOUR_TOKEN&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;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://REPLACE_YOUR_ENDPOINT/lpush/todo/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;todo&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;?_token=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;token&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;fetch&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;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&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="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&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;let&lt;/span&gt; &lt;span class="nx"&gt;result&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="nx"&gt;stringify&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;result&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="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&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="nx"&gt;result&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;Add pages/api/remove.js as below:&lt;br&gt;
&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;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&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="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;req&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="nx"&gt;todo&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="nx"&gt;status&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;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;todo parameter required.&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;let&lt;/span&gt; &lt;span class="nx"&gt;todo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;encodeURI&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;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;todo&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;REPLACE_YOUR_TOKEN&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;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://REPLACE_YOUR_ENDPOINT/lrem/todo/1/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;todo&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;?_token=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;token&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;fetch&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;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&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="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&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;let&lt;/span&gt; &lt;span class="nx"&gt;result&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="nx"&gt;stringify&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;result&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="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&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="nx"&gt;result&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;Update pages/index.js as below:&lt;br&gt;
&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;Head&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;next/head&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Image&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;next/image&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;styles&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;../styles/Home.module.css&lt;/span&gt;&lt;span class="dl"&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;useEffect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useState&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;react&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;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;Home&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="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;setData&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&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;loading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setLoading&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setTodo&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&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;let&lt;/span&gt; &lt;span class="nx"&gt;changeHandler&lt;/span&gt; &lt;span class="o"&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="nx"&gt;setTodo&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;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&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;addTodo&lt;/span&gt; &lt;span class="o"&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="nx"&gt;setLoading&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;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
       &lt;span class="nx"&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;/api/add?todo=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
           &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;res&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="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
               &lt;span class="nx"&gt;loadTodos&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;let&lt;/span&gt; &lt;span class="nx"&gt;removeTodo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rtodo&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="nx"&gt;setLoading&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;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;/api/remove?todo=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;rtodo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
           &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;res&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="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
               &lt;span class="nx"&gt;loadTodos&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;let&lt;/span&gt; &lt;span class="nx"&gt;loadTodos&lt;/span&gt; &lt;span class="o"&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="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="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;load todos&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       &lt;span class="nx"&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;/api/list&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;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;res&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="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                   &lt;span class="nx"&gt;setData&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;setLoading&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="p"&gt;}&lt;/span&gt;

   &lt;span class="nx"&gt;useEffect&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="nx"&gt;setLoading&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;loadTodos&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;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;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Loading...&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="p"&gt;(&lt;/span&gt;
       &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
           &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Head&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
               &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Next&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;js&lt;/span&gt; &lt;span class="nx"&gt;TODO&lt;/span&gt; &lt;span class="nx"&gt;APP&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/title&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;               &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;description&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Generated by create next app&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
               &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;link&lt;/span&gt; &lt;span class="nx"&gt;rel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;icon&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/favicon.ico&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
           &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Head&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;
           &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
               &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                   &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h1&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&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="o"&gt;&amp;gt;&lt;/span&gt;
                       &lt;span class="nx"&gt;TODO&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt; &lt;span class="kd"&gt;with&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://blog.upstash.com/nextjs-todo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Next&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;js&lt;/span&gt;&lt;span class="o"&gt;!&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/a&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                       &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;br&lt;/span&gt;&lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
                       &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;br&lt;/span&gt;&lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
                   &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h1&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                   &lt;span class="p"&gt;{&lt;/span&gt;
                       &lt;span class="nx"&gt;loading&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt;
                           &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;href&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="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                               &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;img&lt;/span&gt; &lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/loader.gif&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
                           &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/a&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                           &lt;span class="p"&gt;:&lt;/span&gt;
                           &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;form&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cardForm&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;onSubmit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;addTodo&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                               &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cardInput&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="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
                                      &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;todo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;onChange&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;changeHandler&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
                                      &lt;span class="nx"&gt;placeholder&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Enter your exciting TODO item!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
                           &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/form&lt;/span&gt;&lt;span class="err"&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;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
                       &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;href&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="nx"&gt;onClick&lt;/span&gt;&lt;span class="o"&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="nx"&gt;removeTodo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                           &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/p&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                       &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/a&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="err"&gt;}
&lt;/span&gt;
               &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;           &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/main&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;
           &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;footer&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;footer&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
               &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;
                   &lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://blog.upstash.com/nextjs-todo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
                   &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;_blank&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
                   &lt;span class="nx"&gt;rel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;noopener noreferrer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
               &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                   &lt;span class="nx"&gt;Powered&lt;/span&gt; &lt;span class="nx"&gt;by&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;span&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;logo&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
           &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Image&lt;/span&gt; &lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/logo.png&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;alt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Upstash Logo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;87&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;         &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/span&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;               &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/a&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;           &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/footer&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;       &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;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 see, it is a basic React application which uses hooks. We have 3 methods which interact with APIs: addTodo, removeTodo and loadTodos.&lt;/p&gt;

&lt;p&gt;And finally update the styles/Home.module.css file as &lt;a href="https://github.com/upstash/examples/blob/master/nextjs-todo/styles/Home.module.css"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Run and Deploy
&lt;/h3&gt;

&lt;p&gt;Run your project locally with &lt;code&gt;npm run dev&lt;/code&gt;. If everything looks good, you can deploy your project by running &lt;code&gt;vercel&lt;/code&gt; in the project folder. Vercel will create serverless functions for your API functions. The default region for Vercel functions is US-EAST-1, that’s why we created our database in the same region.&lt;/p&gt;

&lt;h3&gt;
  
  
  NOTES
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;It is safer to keep the database token in a &lt;a href="https://vercel.com/docs/environment-variables"&gt;Vercel environment variable&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;It is best for performance to keep the serverless function and Redis database in the same region.&lt;/li&gt;
&lt;li&gt;We could use Redis clients instead of REST API. But as I mentioned before, the database connections can cause issues inside serverless functions. Also note that we did not see a major performance difference between Upstash REST API and native API.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>serverless</category>
      <category>nextjs</category>
      <category>redis</category>
      <category>node</category>
    </item>
    <item>
      <title>Session Management on Google Cloud Run with Serverless Redis</title>
      <dc:creator>Enes Akar</dc:creator>
      <pubDate>Mon, 05 Jul 2021 13:12:18 +0000</pubDate>
      <link>https://forem.com/enesakar/session-management-on-google-cloud-run-with-serverless-redis-4in9</link>
      <guid>https://forem.com/enesakar/session-management-on-google-cloud-run-with-serverless-redis-4in9</guid>
      <description>&lt;p&gt;Developers are moving their apps to serverless architectures and one of the most common questions is &lt;a href="https://stackoverflow.com/questions/57711095/are-users-sessions-on-google-cloud-run-apps-directed-to-the-same-instance"&gt;how to store user sessions&lt;/a&gt;. You need to keep your state and session data in an external data store because serverless environments are stateless by design. Unfortunately most of the databases are not serverless friendly. They do not support per-request pricing or they require heavy and persistent connections. These also explain the motivations why we built Upstash. Upstash is a serverless Redis database with per-request pricing, durable storage.&lt;/p&gt;

&lt;p&gt;In this article I will write a basic web application which will run on Google Cloud Run and keep the user sessions in Upstash Redis. Google Cloud Run provides Serverless Container service which is also stateless. Cloud Run is more powerful than serverless functions (AWS Lambda, Cloud Functions) as you can run your own container. But you can not guarantee that the same container instance will process the requests of the same user. So you need to keep the user session in an external storage. Redis is the most popular choice to keep the session data thanks to its speed and simplicity. Upstash gives you the serverless Redis database which fits perfectly to your serverless stack.&lt;/p&gt;

&lt;p&gt;If you want to store your session data manually on Redis, check &lt;a href="https://docs.upstash.com/tutorials/using_google_cloud_functions"&gt;here&lt;/a&gt;. But in this article I will use &lt;a href="https://github.com/expressjs/session"&gt;Express session&lt;/a&gt; middleware which can work with Redis for user session management.&lt;/p&gt;

&lt;p&gt;Here is the &lt;a href="https://cloud-run-sessions-dr7fcdmn3a-uc.a.run.app"&gt;live demo.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is the &lt;a href="https://github.com/upstash/examples/tree/master/cloud-run-sessions"&gt;source code&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Stack
&lt;/h2&gt;

&lt;p&gt;Serverless processing: Google Cloud Run&lt;/p&gt;

&lt;p&gt;Serverless data: Upstash&lt;/p&gt;

&lt;p&gt;Web framework: Express&lt;/p&gt;

&lt;h2&gt;
  
  
  Project Setup
&lt;/h2&gt;

&lt;p&gt;Create a directory for your project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
mkdir cloud-run-sessions

cd cloud-run-sessions

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

&lt;/div&gt;



&lt;p&gt;Create a node project and install dependencies:&lt;br&gt;
&lt;/p&gt;

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

npm install express redis connect-redis express-session

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

&lt;/div&gt;



&lt;p&gt;Create a Redis DB from &lt;a href="https://console.upstash.com"&gt;Upstash&lt;/a&gt;. In the database details page, click the Connect button, copy the connection code (Node.js node-redis).&lt;/p&gt;

&lt;p&gt;If you do not have it already, install Google Cloud SDK as described &lt;a href="https://cloud.google.com/sdk/docs/install"&gt;here.&lt;/a&gt; Set the project and enable Google Run and Build services:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
gcloud config set project cloud-run-sessions

gcloud services enable run.googleapis.com

gcloud services enable cloudbuild.googleapis.com

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Code
&lt;/h2&gt;

&lt;p&gt;Create index.js and update as below:&lt;br&gt;
&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;var&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&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;express&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;parseurl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&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;parseurl&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&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;express-session&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;redis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&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;redis&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;RedisStore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&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;connect-redis&lt;/span&gt;&lt;span class="dl"&gt;'&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="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createClient&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="c1"&gt;// REPLACE HERE&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;()&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;use&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="na"&gt;store&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;RedisStore&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&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="na"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;forest squirrel&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="na"&gt;resave&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;saveUninitialized&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;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&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="nx"&gt;next&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="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;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;views&lt;/span&gt;&lt;span class="p"&gt;)&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;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;views&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="c1"&gt;// get the url pathname&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;pathname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;parseurl&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;pathname&lt;/span&gt;

&lt;span class="c1"&gt;// count the views&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;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;views&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&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;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;views&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&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="kd"&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="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&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="nx"&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;you viewed this page &lt;/span&gt;&lt;span class="dl"&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;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;views&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="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; times&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;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/foo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&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="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&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="nx"&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;you viewed this page &lt;/span&gt;&lt;span class="dl"&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;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;views&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/foo&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; times&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;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/bar&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&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="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&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="nx"&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;you viewed this page &lt;/span&gt;&lt;span class="dl"&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;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;views&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/bar&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; times&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;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8080&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&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="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Example app listening on port 8080!&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;Run the app: &lt;code&gt;node index.js&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Check &lt;a href="http://localhost:3000/foo"&gt;http://localhost:3000/foo&lt;/a&gt; in different browsers to validate it keeps the session.&lt;/p&gt;

&lt;p&gt;Add the start script to your &lt;code&gt;package.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"scripts"&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;"test"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"echo &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;Error: no test specified&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; &amp;amp;&amp;amp; exit 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;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"node index"&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;h2&gt;
  
  
  Build
&lt;/h2&gt;

&lt;p&gt;Create a Docker file (Dockerfile) in the project folder as below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Use the official lightweight Node.js 12 image.
# https://hub.docker.com/_/node
FROM node:12-slim

# Create and change to the app directory.
WORKDIR /usr/src/app

# Copy application dependency manifests to the container image.
# A wildcard is used to ensure both package.json AND package-lock.json are copied.
# Copying this separately prevents re-running npm install on every code change.
COPY package*.json ./

# Install dependencies.
RUN npm install

# Copy local code to the container image.
COPY . ./

# Run the web service on container startup.
CMD [ "npm", "start" ]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Build your container image:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
gcloud builds submit --tag gcr.io/cloud-run-sessions/main

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

&lt;/div&gt;



&lt;p&gt;List your container images: &lt;code&gt;gcloud container images list&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Run the container locally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
gcloud auth configure-docker

docker run -d -p 8080:8080 gcr.io/cloud-run-sessions/main:v0.1

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

&lt;/div&gt;



&lt;p&gt;In case you have an issue on docker run, check &lt;a href="https://cloud.google.com/container-registry/docs/troubleshooting"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploy
&lt;/h2&gt;

&lt;p&gt;Run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
gcloud run deploy cloud-run-sessions \

  --image gcr.io/cloud-run-sessions/main:v0.1 \

  --platform managed \

  --region us-central1 \

  --allow-unauthenticated

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

&lt;/div&gt;



&lt;p&gt;This command should give you &lt;a href="https://cloud-run-sessions-dr7fcdmn3a-uc.a.run.app"&gt;the URL of your application&lt;/a&gt; as below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Deploying container to Cloud Run service [cloud-run-sessions] in project [cloud-run-sessions] region [us-central1]

  ✓ Deploying... Done.                                                                                                                                 

  ✓ Creating Revision...                                                                                                                             

  ✓ Routing traffic...                                                                                                                               

  ✓ Setting IAM Policy...                                                                                                                            

Done.                                                                                                                                                

Service [cloud-run-sessions] revision [cloud-run-sessions-00006-dun] has been deployed and is serving 100 percent of traffic.

Service URL: https://cloud-run-sessions-dr7fcdmn3a-uc.a.run.app

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Cloud Run vs Cloud Functions
&lt;/h2&gt;

&lt;p&gt;I have developed two small prototypes with both. Here my impression:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Simplicity: Cloud functions are simpler to deploy as it does not require any container building step.&lt;/li&gt;
&lt;li&gt;Portability: Cloud Run leverages your container, so anytime you can move your application to any containerized system. This is a plus for Cloud Run.&lt;/li&gt;
&lt;li&gt;Cloud Run looks more powerful as it runs your own container with more configuration options. It also allows running longer tasks (can be extended to 60 minutes)&lt;/li&gt;
&lt;li&gt;Cloud Run looks more testable as you can run the container locally. Cloud Functions require a simulated environment.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Personally, I see Cloud Functions as a pure serverless solution where Cloud Run is a hybrid solution.  I would choose Cloud functions for simple, self contained tasks or event driven solutions. If my use case is more complex with portability/testability requirements, then I would choose Cloud Run.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Build a Serverless Leaderboard API At Edge using Cloudflare Workers and Redis</title>
      <dc:creator>Enes Akar</dc:creator>
      <pubDate>Sat, 03 Jul 2021 08:52:51 +0000</pubDate>
      <link>https://forem.com/enesakar/build-a-serverless-leaderboard-api-at-edge-using-cloudflare-workers-and-redis-26h5</link>
      <guid>https://forem.com/enesakar/build-a-serverless-leaderboard-api-at-edge-using-cloudflare-workers-and-redis-26h5</guid>
      <description>&lt;p&gt;With edge functions, it is possible to run your backend at the closest location to your users. Cloudflare Workers and Fastly Compute@Edge runs your function at the closest location to your user using their CDN infrastructure.&lt;/p&gt;

&lt;p&gt;In this article we will implement a very common web use case at Edge. We will implement a leaderboard API without any backend servers, containers or even serverless functions. We will just use edge functions.  Leaderboard will have the following APIs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  addScore: Adds a score with the player's name. This will write the score to the Upstash Redis directly from the Edge functions.&lt;/li&gt;
&lt;li&gt;  getLeaderBoard: Returns the list of score-player pairs. This call will first check the Edge cache. If the leaderboard does not exist at the Edge Cache then it will fetch it from the Upstash Redis.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Project Setup
&lt;/h2&gt;

&lt;p&gt;In this tutorial, we will use Cloudflare Workers and Upstash. You can create a free database from &lt;a href="https://console.upstash.com"&gt;Upstash Console&lt;/a&gt;. Then create a Workers project using &lt;a href="https://developers.cloudflare.com/workers/get-started/guide"&gt;Wrangler&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Install wrangler: &lt;code&gt;npm install -g @cloudflare/wrangler&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Authenticate: &lt;code&gt;wrangler login&lt;/code&gt; or &lt;code&gt;wrangler config&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Then create a project: &lt;code&gt;wrangler generate edge-leaderboard&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Open &lt;code&gt;wrangler.toml&lt;/code&gt;. Run &lt;code&gt;wrangler whoami&lt;/code&gt; and copy/paste your account id to your wrangler.toml.&lt;/p&gt;

&lt;p&gt;Find your REST token from database details page in the &lt;a href="https://console.upstash.com"&gt;Upstash Console&lt;/a&gt;. Copy/paste your token to your wrangler toml as below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name = "edge-leaderboard"
type = "javascript"

account_id = "REPLACE_YOUR_ACCOUNT_ID"
workers_dev = true
route = ""
zone_id = ""

[vars]
TOKEN = "REPLACE_YOUR_UPSTASH_REST_TOKEN"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Code
&lt;/h2&gt;

&lt;p&gt;The only file we need is the Workers Edge function. Update the index.js as below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fetch&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="o"&gt;=&amp;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;respondWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;handleRequest&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;request&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="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;handleRequest&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="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;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GET&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="nx"&gt;getLeaderboard&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="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;method&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&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="nx"&gt;addScore&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="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;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invalid Request!&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;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;getLeaderboard&lt;/span&gt;&lt;span class="p"&gt;()&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;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://us1-full-bug-31874.upstash.io/zrevrange/scores/0/1000/WITHSCORES/?_token=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;TOKEN&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;res&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;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&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;url&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
   &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="na"&gt;cf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
       &lt;span class="p"&gt;{&lt;/span&gt;
         &lt;span class="na"&gt;cacheTtl&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="na"&gt;cacheEverything&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="na"&gt;cacheKey&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;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="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="nx"&gt;addScore&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="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;searchParams&lt;/span&gt; &lt;span class="p"&gt;}&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;URL&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;url&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;player&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;player&lt;/span&gt;&lt;span class="dl"&gt;'&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;score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;score&lt;/span&gt;&lt;span class="dl"&gt;'&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;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://us1-full-bug-31874.upstash.io/zadd/scores/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;score&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;player&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;?_token=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;TOKEN&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;res&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;fetch&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="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Response&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&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 route the request to two methods: if it is a GET, we return the leaderboard. If it is a POST, we read the query parameters and add a new score.&lt;/p&gt;

&lt;p&gt;In the getLeaderboard() method, you will see we pass a cache configuration to the fetch() method. It caches the result of the request at the Edge for 10 seconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test The API
&lt;/h2&gt;

&lt;p&gt;In your project folder run &lt;code&gt;wrangler dev&lt;/code&gt;. It will give you a local URL. You can test your API with curl:&lt;/p&gt;

&lt;p&gt;Add new scores:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://127.0.0.1:8787&lt;span class="se"&gt;\?&lt;/span&gt;player&lt;span class="se"&gt;\=&lt;/span&gt;messi&lt;span class="se"&gt;\&amp;amp;&lt;/span&gt;score&lt;span class="se"&gt;\=&lt;/span&gt;13

curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://127.0.0.1:8787&lt;span class="se"&gt;\?&lt;/span&gt;player&lt;span class="se"&gt;\=&lt;/span&gt;ronaldo&lt;span class="se"&gt;\&amp;amp;&lt;/span&gt;score&lt;span class="se"&gt;\=&lt;/span&gt;17

curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://127.0.0.1:8787&lt;span class="se"&gt;\?&lt;/span&gt;player&lt;span class="se"&gt;\=&lt;/span&gt;benzema&lt;span class="se"&gt;\&amp;amp;&lt;/span&gt;score&lt;span class="se"&gt;\=&lt;/span&gt;18
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Get the leaderboard:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s1"&gt;'\n Latency: %{time_total}s\n'&lt;/span&gt; http://127.0.0.1:8787
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Call the “curl -w '\n Total: %{time_total}s\n' &lt;a href="http://127.0.0.1:8787"&gt;http://127.0.0.1:8787&lt;/a&gt;” multiple times. You will see the latency becomes very small with the next calls as the cached result comes from the edge.&lt;/p&gt;

&lt;p&gt;If you wait more than 10 seconds then you will see the latency becomes higher as the cache is evicted and the function fetches the leaderboard from the Upstash Redis again.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploy The API
&lt;/h2&gt;

&lt;p&gt;First change the type in the wrangler.toml to &lt;code&gt;webpack&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name = "edge-leaderboard"
type = "webpack"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, run &lt;code&gt;wrangler publish&lt;/code&gt;. Wrangler will output the URL. If you want to deploy to a custom domain see &lt;a href="https://developers.cloudflare.com/workers/get-started/guide#optional-configure-for-deploying-to-a-registered-domain"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Coming Soon: Built in Edge Caching
&lt;/h2&gt;

&lt;p&gt;In this example, we have used Cloudflare Cache to cache the Redis result at the edge. Soon, Upstash will provide a separate REST URL which will be cached at the Edge by default. So Upstash users will be able to access Edge caching in all environments.&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>edgecomputing</category>
      <category>node</category>
      <category>cloudflare</category>
    </item>
    <item>
      <title>Redis at the Edge with Cloudflare Workers</title>
      <dc:creator>Enes Akar</dc:creator>
      <pubDate>Wed, 16 Jun 2021 04:05:52 +0000</pubDate>
      <link>https://forem.com/enesakar/redis-st-the-edge-with-cloudflare-workers-20bl</link>
      <guid>https://forem.com/enesakar/redis-st-the-edge-with-cloudflare-workers-20bl</guid>
      <description>&lt;p&gt;Computing at the Edge is one of the most exciting capabilities in recent years. CDN allows you to keep your files closer to your users. Edge computing allows you to run your applications closer to your users. This helps developers to build globally distributed, performant applications.&lt;/p&gt;

&lt;p&gt;Cloudflare Workers is the leading product in this space right now. It gives you a serverless processing environment without cold starts. You leverage Cloudflare's global network to minimize latency of your applications. You can write your functions in Javascript, Rust, C and C++.&lt;/p&gt;

&lt;p&gt;Similar to Serverless functions (AWS Lambda etc.), Cloudflare Workers are stateless. As you can see in &lt;a href="https://workers.cloudflare.com/node"&gt;Cloudflare’s survey&lt;/a&gt;, developers are asking ways to connect their databases from Edge functions. Unfortunately, most databases are not designed for serverless environments, they require persistent connections. We developed the REST API over Redis to enable serverless edge functions to access Upstash in the simplest and fastest way possible.&lt;/p&gt;

&lt;h3&gt;
  
  
  Comparing with Cloudflare Workers KV
&lt;/h3&gt;

&lt;p&gt;Cloudflare has a basic Key Value store that you can use to store the state of your Edge functions. Upstash Redis excels against Cloudflare KV in several aspects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Cloudflare KV provides only basic get/set/delete functionality. Upstash gives you all Redis data structures where you can build much more sophisticated functionality (Hashes, Lists, SortedSets, ranges, appends, increments etc).&lt;/li&gt;
&lt;li&gt;  Cloudflare KV is designed to be accessible from the Cloudflare ecosystem while you can access and consume the Upstash Redis from anywhere as it supports both Redis and REST API. You can offload your data from Edge to Redis then process by any Redis client.&lt;/li&gt;
&lt;li&gt;  Cloudflare KV is optimized for read heavy applications. The writes can take 60 seconds to be replicated to other locations. The write latencies are in milliseconds for Upstash.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now let’s write a simple example to showcase the power of Cloudflare+Redis combination.&lt;/p&gt;

&lt;h3&gt;
  
  
  Analytics at Edge
&lt;/h3&gt;

&lt;p&gt;Web analytics is a very common need for all website owners. Google Analytics is powerful but many developers do not like sharing their traffic data with Google and injecting cookies to users’ browsers. As your website traffic comes through CDN, you can easily track your traffic at the Edge layer. Here we will implement a very simple example to showcase tracking user traffic from Cloudflare Workers. We will intercept the traffic in the Cloudflare Workers and save the user requests to Upstash Redis. Then we will write a basic standalone application which will analyse the requests and give me the following information on a selected day:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Page views&lt;/li&gt;
&lt;li&gt;  Unique visitors&lt;/li&gt;
&lt;li&gt;  Top countries with the most number of visitors&lt;/li&gt;
&lt;li&gt;  Most visited pages&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;If you do not have one, create a database following this &lt;a href="https://docs.upstash.com/"&gt;guide&lt;/a&gt;. In the database page, click the button REST API and copy the REST URL. If you have a Cloudflare account, you can create a Workers function or you can use &lt;a href="https://cloudflareworkers.com/#36ebe026bf3510a2e5acace89c09829f:about:blank"&gt;the playground&lt;/a&gt; without an account too.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cloudflare Workers Code
&lt;/h3&gt;

&lt;p&gt;Cloudflare Workers function accepts &lt;code&gt;request&lt;/code&gt; as a parameter. Using the REST API of Upstash, I record the request to a Redis List. I use the current date as the key for the Redis List. So we record the requests per day in separate Lists.&lt;/p&gt;

&lt;p&gt;Upstash REST API requires an endpoint and a token. After creating a database, you can copy the endpoint and token from &lt;a href="https://console.upstash.com/"&gt;the console&lt;/a&gt; clicking to the &lt;code&gt;REST API&lt;/code&gt; button. Update your Cloudflare Workers function with the below code, replacing endpoint and token:&lt;br&gt;
&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;endpoint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;REPLACE_UPSTASH_REST_ENDPOINT&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;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;REPLACE_UPSTASH_REST_TOKEN&lt;/span&gt;&lt;span class="dl"&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="nx"&gt;recordRequest&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="p"&gt;{&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&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;datestr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getFullYear&lt;/span&gt;&lt;span class="p"&gt;()&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="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getMonth&lt;/span&gt;&lt;span class="p"&gt;()&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="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;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getDate&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;data&lt;/span&gt; &lt;span class="o"&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;url&lt;/span&gt;&lt;span class="dl"&gt;"&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;url&lt;/span&gt;&lt;span class="p"&gt;],&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="kd"&gt;let&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;endpoint&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/lpush/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;datestr&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;init&lt;/span&gt; &lt;span class="o"&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="nx"&gt;stringify&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="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="s2"&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;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="s2"&gt;Authorization&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;Bearer &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;token&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;fetch&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="nx"&gt;init&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="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;handleRequest&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="p"&gt;{&lt;/span&gt;
 &lt;span class="nx"&gt;recordRequest&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="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;My Awesome Website&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;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fetch&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;event&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="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;respondWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
   &lt;span class="nx"&gt;handleRequest&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;request&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="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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&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;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&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="mi"&gt;500&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;h3&gt;
  
  
  Analytics Tool Code
&lt;/h3&gt;

&lt;p&gt;Now let’s write a simple command line application which will take the date as an argument and return us analytics data. Create a folder and run &lt;code&gt;npm init&lt;/code&gt;. Then install the Redis client with &lt;code&gt;npm install ioredis&lt;/code&gt;. Copy &lt;code&gt;.env.example&lt;/code&gt; as &lt;code&gt;.env&lt;/code&gt; file and set your Redis URL (ioredis). Update the index.js with below:&lt;br&gt;
&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;Redis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ioredis&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&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;dotenv&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;config&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="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;EDGE analytics with CloudFlare Workers and Upstash Redis.&lt;/span&gt;&lt;span class="dl"&gt;"&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;redis&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;Redis&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;REDIS_URL&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;dateArg&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;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&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;datestr&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;dateArg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="nx"&gt;datestr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dateArg&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="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
   &lt;span class="nx"&gt;datestr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getFullYear&lt;/span&gt;&lt;span class="p"&gt;()&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="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getMonth&lt;/span&gt;&lt;span class="p"&gt;()&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="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;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getDate&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lrange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;datestr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0&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;-1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&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;result&lt;/span&gt;&lt;span class="p"&gt;)&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;pageview&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&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;users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Set&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;countries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Map&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;paths&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Map&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;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="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Could not connect to Redis.&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="nx"&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="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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Upstash Redis Connection Successful. Analyzing the access logs...&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;result&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;result&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;===&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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Could not find any access logs for the date:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;datestr&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;for&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;elem&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&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;entries&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="nx"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;elem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
               &lt;span class="k"&gt;for&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;entry&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;entries&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;entry&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="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;accept&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;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&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;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                           &lt;span class="nx"&gt;pageview&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&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="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cf-ipcountry&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;let&lt;/span&gt; &lt;span class="nx"&gt;temp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;countries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&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="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;temp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                           &lt;span class="nx"&gt;temp&lt;/span&gt; &lt;span class="o"&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;countries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&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;temp&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="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;entry&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="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;url&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;let&lt;/span&gt; &lt;span class="nx"&gt;temp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&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="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;temp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                           &lt;span class="nx"&gt;temp&lt;/span&gt; &lt;span class="o"&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;paths&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&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;temp&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="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;entry&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="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;x-real-ip&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;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&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="p"&gt;}&lt;/span&gt;
           &lt;span class="nx"&gt;countries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;([...&lt;/span&gt;&lt;span class="nx"&gt;countries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;()].&lt;/span&gt;&lt;span class="nx"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&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;b&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="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;a&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="nx"&gt;paths&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;([...&lt;/span&gt;&lt;span class="nx"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;()].&lt;/span&gt;&lt;span class="nx"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&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;b&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="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;a&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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;DATE: &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;datestr&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="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;PAGE VIEWS: &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;pageview&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="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;UNIQUE VISITORS: &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
           &lt;span class="nx"&gt;logMap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;TOP COUNTRIES&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;countries&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
           &lt;span class="nx"&gt;logMap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;TOP PAGES&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;paths&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="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;logMap&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="nx"&gt;data&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="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;title&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="nx"&gt;log&lt;/span&gt;&lt;span class="p"&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="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="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mapEntriesToString&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="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;mapEntriesToString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entries&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="nb"&gt;Array&lt;/span&gt;
       &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;k&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;v&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\n`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can run your application with &lt;code&gt;node index 2021-6-16&lt;/code&gt; or just &lt;code&gt;node index&lt;/code&gt;. The latter one will query for today.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ceOAHfNS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.upstash.com/img/blog/edge-command.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ceOAHfNS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.upstash.com/img/blog/edge-command.png" alt="command edge" title="command edge"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can think of this example as a starting point. You can develop a rich web application with charts and tables according to your analytics needs. You can use other Redis data structures for more powerful analysis.&lt;/p&gt;

&lt;h3&gt;
  
  
  Upstash Edge Roadmap
&lt;/h3&gt;

&lt;p&gt;REST API is the first step in our Edge story. We are planning two important developments this year.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Edge Caching: Right now, all your REST requests come to your database region. We will support Edge Caching soon, so your REST requests will be cached at all edge locations globally. This will enable Upstash Redis to provide low latency all around the world like the Cloudflare KV.&lt;/li&gt;
&lt;li&gt;  Global Replicated Databases: Global (multi region) replication will replicate your data to multiple regions. So all requests (both Redis and REST API) will go to the closest location. This will give you low latency globally with the least sacrifice on consistency.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I am planning to write a blog post dedicated to our Edge roadmap. Stay tuned and follow us on &lt;a href="https://twitter.com/upstash"&gt;twitter&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>cloudflare</category>
      <category>redis</category>
      <category>edge</category>
    </item>
  </channel>
</rss>
