<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Oscar</title>
    <description>The latest articles on DEV Community by Oscar (@ozcap).</description>
    <link>https://dev.to/ozcap</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%2F822864%2F677256dd-a585-471f-9a87-8ae173780173.png</url>
      <title>DEV Community: Oscar</title>
      <link>https://dev.to/ozcap</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ozcap"/>
    <language>en</language>
    <item>
      <title>How to deploy a NextJS app on AWS Fargate with Terraform</title>
      <dc:creator>Oscar</dc:creator>
      <pubDate>Fri, 08 Sep 2023 15:49:36 +0000</pubDate>
      <link>https://dev.to/ozcap/how-to-deploy-a-nextjs-app-on-aws-fargate-with-terraform-jif</link>
      <guid>https://dev.to/ozcap/how-to-deploy-a-nextjs-app-on-aws-fargate-with-terraform-jif</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In this guide, you will learn how to deploy a NextJS Node server on AWS ECS Fargate. This is an easy and scalable way to deploy a Dockerised NextJS project. It also makes it easy to add more environments and keep a clean separation between them while making changes to them all through a git repository (no dashboard fiddling!). This is my personal approach to deploying new projects &lt;/p&gt;

&lt;p&gt;If you’re reading this, then you’ve probably already got your own reason to want to deploy NextJS on AWS (with Terraform). If not, here are some good ones:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No Github account restrictions - no matter if you are deploying from a org repo or a private, AWS treats it all the same (unlike Vercel).&lt;/li&gt;
&lt;li&gt;Easily manage sensitive environment variables with Github secrets and spin up multiple separate environments.&lt;/li&gt;
&lt;li&gt;Use node in-memory cache straight out of the box. Your server will have a persistent state (unlike Lambda!) which means database connections are also no problem.&lt;/li&gt;
&lt;li&gt;Infrastructure as code - clone the sample repo, add your own AWS keys and push to Github. Simple as that!&lt;/li&gt;
&lt;li&gt;Use up some of those AWS credits you have lying around and collecting dust.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You will learn how to get started right away, and then the steps after, I will try to explain what is going on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quickstart
&lt;/h2&gt;

&lt;p&gt;I will cut to the business and show you how to get up and running as quickly as possible. It will be a simple process and you will not have to touch the AWS console… Much!&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Get your AWS keys&lt;/strong&gt; — Get hold of your &lt;code&gt;AWS_ACCESS_KEY_ID&lt;/code&gt; and &lt;code&gt;AWS_SECRET_ACCESS_KEY&lt;/code&gt; credentials from your AWS account. Store these in a safe place, you will need them later.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Create an S3 bucket&lt;/strong&gt; — This will be needed for your remote Terraform state. Leave everything as default and give it a unique name like “&lt;strong&gt;[your name]-terraform-state&lt;/strong&gt;”. Remember this name!&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/OZCAP/nextjs-fargate-terraform"&gt;Clone my repo&lt;/a&gt; (make sure to give it a star first ⭐️) and push a private copy to your own Github account.&lt;/li&gt;
&lt;li&gt;In the cloned repo, edit the backend.tf file, change &lt;code&gt;bucket = "YOUR_BUCKET_NAME”&lt;/code&gt; to the value you created earlier. Commit and push.&lt;/li&gt;
&lt;li&gt;In the settings tab for your newly-published repository, click on &lt;strong&gt;Secrets and variables&lt;/strong&gt; &amp;gt; &lt;strong&gt;Actions&lt;/strong&gt;. Add the following secrets secrets: &lt;code&gt;AWS_ACCESS_KEY_ID&lt;/code&gt; and &lt;code&gt;AWS_SECRET_ACCESS_KEY&lt;/code&gt; (the ones from step 1).&lt;/li&gt;
&lt;li&gt;You are now ready to deploy — In the &lt;strong&gt;Actions&lt;/strong&gt; tab of your repo, you should see an action called “Deploy Prod” on the sidebar. Click the action and then press run workflow. Wait for it to complete and &lt;strong&gt;your app should now be deployed!&lt;/strong&gt; But where is it?&lt;/li&gt;
&lt;li&gt;You can find the temporary URL that hosts your site by going onto your &lt;strong&gt;AWS dashboard &amp;gt; EC2 &amp;gt; Load balancers &amp;gt; (your load balancer)&lt;/strong&gt;. Where it says &lt;strong&gt;DNS Name&lt;/strong&gt;, that is your app’s live url. We will talk later about routing this through a domain with a DNS of your choice (like Cloudflare).&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;

&lt;p&gt;So you’ve just deployed the app, but what is actually going on? If you read through the Github workflow, you can get a rough idea, but here is a diagram to show in slightly more detail.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4oPbv7Yv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gjkd5f8ll6jwv2rmf158.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4oPbv7Yv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gjkd5f8ll6jwv2rmf158.jpg" alt="CI of NextJS deployment on AWS Fargate" width="800" height="493"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Firstly, an ECR repository is provisioned with terraform (&lt;code&gt;ecr.tf&lt;/code&gt;). An ECR repo is just a directory which holds tagged docker images. The Docker image is then built and pushed to the ECR for later use. Afterwards, the rest of the terraform infrastructure if provisioned (load balancer, ECS, security groups, etc) and a new ECS task is created, which references the just-uploaded Docker image. The ECS the spins up the docker image as a Fargate task with the given parameters (and environment variables) specified in the task definition.&lt;/p&gt;

&lt;h2&gt;
  
  
  Terraform and multiple environments
&lt;/h2&gt;

&lt;p&gt;Looking in the &lt;code&gt;terraform&lt;/code&gt; directory, you will see that there are two subdirectories: &lt;code&gt;module&lt;/code&gt; and &lt;code&gt;prod&lt;/code&gt;. The prod directory is the entry-point of the Terraform. If you look in &lt;code&gt;main.tf&lt;/code&gt;, you will see that the main sources the &lt;code&gt;module&lt;/code&gt; dir while injecting a series of custom variables. The idea is that if you copy and paste the &lt;code&gt;prod&lt;/code&gt; directory as &lt;code&gt;dev&lt;/code&gt; (for example) and find and replace all instances of prod in that dir to dev, you will be able to spin up a second environment which can source the terraform module in the same way!&lt;/p&gt;

&lt;p&gt;Runtime environment variables are injected into the task definition (&lt;code&gt;task.tf&lt;/code&gt;) and are loaded in on the &lt;code&gt;terraform plan&lt;/code&gt; workflow step of the Github action. You can make any variable visible to Terraform by adding a &lt;code&gt;TF_VAR_&lt;/code&gt; prefix. Terraform will ignore the TF_VAR_ prefix and interpret the rest as a variable. All variables loaded from actions need to be defined in &lt;strong&gt;prod/variables.tf&lt;/strong&gt; and all variables referenced in &lt;strong&gt;terraform/module&lt;/strong&gt; need to be injected by &lt;code&gt;main.tf&lt;/code&gt; and also defined in &lt;strong&gt;module/variables.tf&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using a custom domain
&lt;/h2&gt;

&lt;p&gt;Hosting your app from an AWS-generated, non-HTTPS load balancer url is not ideal. If you want to use a custom domain, you can add that domain to Cloudflare, and then create a &lt;code&gt;cname&lt;/code&gt; DNS record which points to this load balancer url. You should now be able to access your app from a HTTPS connection on your custom domain. To prevent internet access from the original load balancer domain, you can blacklist all IP addresses, apart from IP ranges owned by Cloudflare. This ensures that the only route to your site is through Cloudflare’s network.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding a Postgresql database
&lt;/h2&gt;

&lt;p&gt;The great thing about Terraform is that when you want to add new infra, you don’t have to lay a finger on the AWS dashboard. We can spin up an RDS database and load the &lt;code&gt;DATABASE_URL&lt;/code&gt; straight into our app, just by changing a few lines. &lt;/p&gt;

&lt;p&gt;To add the database, search in the whole repo for the words &lt;strong&gt;“UNCOMMENT FOR DATABASE”&lt;/strong&gt; and you will see indicated lines to uncomment. After doing this, a postgres database should spin up on the next deployment.&lt;/p&gt;

&lt;p&gt;If you want to pass the environment variable &lt;code&gt;DATABASE_URL&lt;/code&gt; into your app, add the following lines to the environment array in your ECS task definition.&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="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;task.tf&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"DATABASE_URL"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"postgresql://${var.db_username}:${var.db_password}@${aws_db_instance.db.endpoint}"&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="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;WARNING:&lt;/strong&gt; This RDS instance will be an all-open access. In a production environment, you will not want to expose your database to the internet and instead hide it behind a VPC or narrowly limit the IP addresses which can connect to it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Hopefully this has been a useful guide to get you up and running with Terraform and AWS Fargate. Terraform can be a little bit of a learning curve, but once you understand how it executes and how it manages state, it becomes an valuable and flexible tool. If you have any specific questions or feedback about this article, you can get in touch with me via &lt;a href="https://oscars.dev"&gt;oscars.dev&lt;/a&gt; and in case you missed it before, here is a link to my repo to get started with NextJS, Fargate and Terraform. Enjoy! &lt;a href="https://github.com/OZCAP/nextjs-fargate-terraform"&gt;https://github.com/OZCAP/nextjs-fargate-terraform&lt;/a&gt;&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>aws</category>
      <category>terraform</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Building bulletproof ExpressJS APIs with Zod</title>
      <dc:creator>Oscar</dc:creator>
      <pubDate>Sat, 18 Feb 2023 14:27:01 +0000</pubDate>
      <link>https://dev.to/ozcap/building-bulletproof-expressjs-apis-with-zod-5c1n</link>
      <guid>https://dev.to/ozcap/building-bulletproof-expressjs-apis-with-zod-5c1n</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;As a fullstack developer, building robust API endpoints with good error handling is a very important skill. This post will show how the Zod schema validation/declaration library can be used to make solid API endpoints with good error feedback in a few lines of code.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Zod?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.npmjs.com/package/zod" rel="noopener noreferrer"&gt;Zod is a schema validation npm library used to check types at runtime&lt;/a&gt;. Sounds neat but why do we need this? Isn’t that exactly what Typescript is for? Well Typescript types are great, but they only really help during development. While Typescript types do offer some runtime checking, it is not as comprehensive as Zod. Specifically, Typescript types only validate that the expected properties exist, while Zod can validate the values of those properties.&lt;/p&gt;

&lt;p&gt;Here’s an example of a Zod schema object:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&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="na"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="na"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;email&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;We can then process some data using this schema with the safeParse() function&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&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;unknown&lt;/span&gt; &lt;span class="o"&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;parsedData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;safeParse&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parsedData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;success&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;safeData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;parsedData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;
    &lt;span class="c1"&gt;// safeData passes validation and is safe to use! ☑️&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By parsing the data through our Zod schema object, we can guarantee that &lt;code&gt;safeData&lt;/code&gt; object contains a user with a firstName string property of at least 2 characters long, a lastName property (either string or undefined), an integer age and a valid email address. All this is done in a few lines of code. For the next step, we will look at how we can implement this on an Express JS endpoint.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using Zod with Express
&lt;/h2&gt;

&lt;p&gt;For this demo, we will validate common request headers on an Express route using Zod using middleware. Any handler in the same context of this middleware will also have the same validation. We will start this task by making a schema for our validation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Schema
&lt;/h3&gt;

&lt;p&gt;We want to make sure every request has a header &lt;code&gt;guest-user-id&lt;/code&gt; with a prefix of &lt;code&gt;gid-&lt;/code&gt; and a minimum length of 12 characters.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;requestSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&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="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;guest-user-id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gid-&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Middleware
&lt;/h3&gt;

&lt;p&gt;Next, we want to create a middleware function which checks the input data against the schema and returns a &lt;code&gt;400 (bad request)&lt;/code&gt;  if the input is invalid.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&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="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;input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;requestSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;safeParse&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="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;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;success&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;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="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&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;issues&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;locals&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;next&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;This function sends all of the issues with the Zod parsing as a response which outlines the parameters which did were not valid and why.  We then set the cleaned data in &lt;code&gt;res.locals&lt;/code&gt;. This does not send any data back but it is just a way of setting custom data in our request lifecycle which can be picked up at any point later down the line.&lt;/p&gt;

&lt;p&gt;For example, if we send a bad request with the &lt;code&gt;guest-user-id&lt;/code&gt; header set with a value of &lt;code&gt;guest-123&lt;/code&gt;, we get a full list of all of the issues with our request. This makes debugging issues very easy.&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"too_small"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"minimum"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"inclusive"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"exact"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"String must contain at least 12 character(s)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"path"&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;"headers"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"guest-user-id"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"invalid_string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"validation"&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;"startsWith"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gid-"&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;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Invalid input: must start with &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;gid-&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&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;"path"&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;"headers"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"guest-user-id"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Warning: Do not include sensitive information in the Zod validation schema (like API tokens) while returning &lt;code&gt;input.error.issues&lt;/code&gt;, as requestors will be able to see why their request was rejected and thus, exposing the sensitive information. These checks should be handled separately with the appropriate error code (like HTTP 401) and Zod errors should not be sent back.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Endpoint handler
&lt;/h3&gt;

&lt;p&gt;Finally, we can access the safe input on the handler from the &lt;code&gt;res.locals&lt;/code&gt; where we set it in the middleware. We can also make this input Typescript safe by defining the input as the inferred type of the Zod schema.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&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;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;input&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;locals&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;infer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;requestSchema&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;guestUserId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;guest-user-id&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Your guest user ID is &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;guestUserId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  All together now!
&lt;/h2&gt;

&lt;p&gt;Here is the full endpoint with all of the above steps put together. As you can see, in not very many lines of code, we have a robust and strongly-typed API.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// schema&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;requestSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&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="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;guest-user-id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gid-&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="c1"&gt;// middleware&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&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="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;input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;requestSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;safeParse&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="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;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;success&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;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="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&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;issues&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;locals&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// endpoint handler&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&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;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;input&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;locals&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;infer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;requestSchema&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;guestUserId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;guest-user-id&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Your guest user ID is &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;guestUserId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The old-school alternative
&lt;/h2&gt;

&lt;p&gt;If we wanted to implement the same safety without Zod, our middleware function would look something like this. That’s a lot of code to validate one property. If we have multiple properties, you can imagine how large this validation function would be! This type of validation is prone to errors and can quickly become unwieldy when dealing with multiple properties, whereas Zod provides a more streamlined and less error-prone solution.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&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="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;guestUserId&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;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;guestUserId&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="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;guestUserId&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&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="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;guestUserId is missing&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;guestUserId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;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="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;guestUserId is too short&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;guestUserId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gid-&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&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="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;guestUserId is invalid&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;locals&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;guestUserId&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="nf"&gt;next&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;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;In this blog post, we explored how to use Zod, a TypeScript-first schema validation library, to validate data in Express APIs. We looked at how Zod makes it easy to define and validate data schemas on the fly, and how it can help catch errors early in the development process. By integrating Zod with Express, we can ensure that only valid data is accepted by our APIs, and that any invalid data is rejected with informative error messages in few lines of code.&lt;/p&gt;

&lt;p&gt;Validating data is an essential part of building secure and reliable APIs, and Zod provides a powerful and convenient way to do so. By adopting best practices like data validation, we can build more robust applications and avoid common security vulnerabilities.&lt;/p&gt;

&lt;p&gt;If you're interested in learning more about building secure and reliable software, &lt;a href="https://blog.oscars.dev/posts/hacking_bereal_with_man_in_the_middle/" rel="noopener noreferrer"&gt;be sure to check out my other article on hacking BeReal&lt;/a&gt;. In it, we explore Bereal’s API endpoints and intercept requests to upload whatever photos we like.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>openai</category>
      <category>chatgpt</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>Create Vite/NextJs projects with Tailwind pre-configured, in a single command!</title>
      <dc:creator>Oscar</dc:creator>
      <pubDate>Fri, 19 Aug 2022 07:22:00 +0000</pubDate>
      <link>https://dev.to/ozcap/create-vitenextjs-projects-with-tailwind-pre-configured-in-a-single-command-5f76</link>
      <guid>https://dev.to/ozcap/create-vitenextjs-projects-with-tailwind-pre-configured-in-a-single-command-5f76</guid>
      <description>&lt;p&gt;While boiler-plating react projects over and over again with the same dependencies, I decided to make a tool which would make my life (and hopefully some other peoples’ lives!) slightly easier. Not only does this tool install Tailwind as a dependancy but it also configures your project's CSS and Tailwind config asynchronously so it works straight out of the box!&lt;br&gt;
I also decided to make this tool Typescript as default because we all love &lt;strong&gt;strict&lt;/strong&gt; type-safety ❤.&lt;/p&gt;
&lt;h3&gt;
  
  
  Introducing Quick Init!
&lt;/h3&gt;

&lt;p&gt;Quick-init makes creating a Vite/Next-Typescript-Tailwind project as easy as &lt;code&gt;quick-init [name]&lt;/code&gt;! ⚡ &lt;em&gt;(Just add &lt;code&gt;-t next&lt;/code&gt; for NextJs).&lt;/em&gt; &lt;/p&gt;

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

&lt;p&gt;Aside from wanting to build a tool which I would personally find useful, I took the opportunity to build Quick-init in Rust in order to improve my &lt;em&gt;lacking&lt;/em&gt; familiarity with the language and to also make it &lt;em&gt;blazingly fast™&lt;/em&gt;, as building something useful with a clear functionality goal is the best way to learn something!&lt;/p&gt;

&lt;p&gt;It is also fully-customisable with a local configuration file so you can define which dependencies you want to be auto-installed for each template.&lt;/p&gt;
&lt;h2&gt;
  
  
  Installation (Homebrew)
&lt;/h2&gt;

&lt;p&gt;If you want to install this on Mac OSX with Homebrew, you can install it via a single line on your favourite terminal!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;brew tap ozcap/quick-init &amp;amp;&amp;amp; brew install quick-init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Source Code
&lt;/h2&gt;

&lt;p&gt;If you want to build this from source, make a pull request or simply browse the code behind this then you are more than welcome to see the &lt;a href="https://github.com/ozcap/quick-init" rel="noopener noreferrer"&gt;Github repository!&lt;/a&gt; (Feel free to give a star... Or not).&lt;/p&gt;

</description>
      <category>vite</category>
      <category>react</category>
      <category>nextjs</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Hacking BeReal - A practical lesson on “Man in the Middle” attacks</title>
      <dc:creator>Oscar</dc:creator>
      <pubDate>Thu, 18 Aug 2022 16:34:00 +0000</pubDate>
      <link>https://dev.to/ozcap/hacking-bereal-a-practical-lesson-on-man-in-the-middle-attacks-10b6</link>
      <guid>https://dev.to/ozcap/hacking-bereal-a-practical-lesson-on-man-in-the-middle-attacks-10b6</guid>
      <description>&lt;h2&gt;
  
  
  What is BeReal?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://bereal.com/en" rel="noopener noreferrer"&gt;BeReal&lt;/a&gt; is a new social media app which is said to be more “in the moment” compared to conventional social media platforms. The premise of the app is that once per day, everyone gets a notification &lt;strong&gt;(at the same time)&lt;/strong&gt; saying &lt;em&gt;“It’s time to Be Real!”&lt;/em&gt; Everyone then has two minutes to take a picture of what they’re doing at that moment. The phone simultaneously snaps a front and back camera photo, before uploading it to their feed for all (friends) to see. There is no option to choose existing photos to upload or make edits to photos. You must post as you are, in the moment.&lt;/p&gt;

&lt;p&gt;I wanted to see how much I could manipulate BeReal with the use of a software called &lt;a href="https://mitmproxy.org/" rel="noopener noreferrer"&gt;Mitmproxy&lt;/a&gt; between my iPhone and my Macbook. This software allows me to see all unencrypted HTTPS requests made between my phone and the internet. With this tool, I have the ability to view, pause, edit and cancel any requests at my will. This software also has a Python API for writing custom scripts which I will touch on later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Notable domains
&lt;/h2&gt;

&lt;p&gt;I spent some time using the app and learning which endpoints were associated with which functions. With this information, I am able to build a profile of how this app works. I knew already that BeReal was built on top of Firebase but there seem to be an additional endpoint to handle certain requests.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;firebasestorage.googleapis.com&lt;/strong&gt; - Firebase cloud storage, used to upload files (images).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;us-central1-alexisbarreyat-bereal.cloudfunctions.net&lt;/strong&gt; - Firebase cloud function endpoint, used to create posts, delete posts, set post captions, send reactions, etc.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;mobile.bereal.com&lt;/strong&gt; - Secondary endpoint - Used to get posts feed, view friends, add friends.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Hijacking authentication tokens
&lt;/h2&gt;

&lt;p&gt;The requests made from the phone are authenticated using a &lt;a href="https://jwt.io/" rel="noopener noreferrer"&gt;(JSON Web Token) JWT&lt;/a&gt; which is sent in the “Authorization” header of each request. this header expires after a few minutes, however until then, I can use this token to do whatever I like as an authenticated user.&lt;/p&gt;

&lt;p&gt;BeReal’s most commonly fetched endpoint is &lt;strong&gt;mobile.bereal.com/api/feeds/friends&lt;/strong&gt; which gets the list of friends’ posts on a user’s feed. If I call this endpoint from Postman without the authorization header, I instantly get a &lt;code&gt;403 - Forbidden&lt;/code&gt; status code which is expected, as I have no credentials to say who I am.&lt;/p&gt;

&lt;p&gt;On the Mitmproxy feed, I can just copy and paste the “Authorization” JWT included in any of the BeReal API requests and use that for myself. Upon including this header in Postman, I am allowed through the security and I get greeted with a nice JSON of all of my friends’ posts with links to all of their Images, location data, how many retakes they took, etc.&lt;/p&gt;

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

&lt;p&gt;I can use this same approach to set the caption of my BeReal post as many times as I like. Normally, you are only allowed to set it one time and then it is permanent; however, by making a &lt;code&gt;POST&lt;/code&gt; request to the &lt;strong&gt;/setCaptionPost&lt;/strong&gt; endpoint I can bypass this rule and set my caption over and over again at my leisure.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Custom RealMoji
&lt;/h2&gt;

&lt;p&gt;To show appreciation in BeReal, instead of sending a “Like”, you send a “RealMoji” which is an image of your face as a reaction. This adds a level of personality to a reaction which is normally missed. The fact that you can retake RealMojis as much as you like makes it a good opportunity to insert custom images as reactions as we are not only limited to one per day, unlike the BeReal post. Let’s start by looking at what is happening here when we send a RealMoji.&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;POST&lt;/code&gt; request is made to the firebase storage endpoint which initiates a file upload request.&lt;/p&gt;

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

&lt;p&gt;A &lt;code&gt;PUT&lt;/code&gt; request is made to firebase, using the upload ID returned from the previous request to upload the image. The actual image data is sent in the body of this request.&lt;/p&gt;

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

&lt;p&gt;A final POST request is sent to a cloud function with details of the reaction to be sent (Image storage location, reaction type, etc). Atotal of three requests are necessary to send a RealMoji reaction.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Manipulating expected content size
&lt;/h3&gt;

&lt;p&gt;Without fully understanding the workings of Firebase file uploads, I started by simply modifying the data of the &lt;code&gt;PUT&lt;/code&gt; request, as that is when the image content is sent to the server.&lt;/p&gt;

&lt;p&gt;I set a filter in Mitmproxy to pause the request as it comes through and then modified the body of the request to contain the data of my chosen Jpeg&lt;/p&gt;

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

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

&lt;p&gt;I was then met with a 400 status error, stating that the size of the content did not match what it was expecting. Hmm…&lt;/p&gt;

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

&lt;p&gt;I searched for this number &lt;strong&gt;45243&lt;/strong&gt; and found it defined on the prior request. For my second attempt, I also modified this &lt;code&gt;POST&lt;/code&gt; request in order to match the content size of the file to be uploaded.&lt;/p&gt;

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

&lt;p&gt;After doing this, I received a 200 status on both requests. Success. My manipulated payload has been uploaded! I then refreshed the app to see my “FakeMoji” in all its glory.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Custom BeReal Post
&lt;/h2&gt;

&lt;p&gt;The flow of uploading a BeReal and sending a RealMoji is mostly identical. After working out how to manipulate the firebase upload, creating a custom post was basically no different.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Automating
&lt;/h2&gt;

&lt;p&gt;Mitmproxy also has a Python API which is great for manipulating HTTP requests automatically. Based on the process above, I wrote a script which automates the process of intercepting the request and replacing the data. &lt;/p&gt;

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

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;mitmproxy.http&lt;/span&gt;

&lt;span class="n"&gt;BASE_PATH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dirname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abspath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__file__&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;FAKEMOJI&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BASE_PATH&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;fakemoji.jpg&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;read_content&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
       &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;rb&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;   

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Interceptor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;upload_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

    &lt;span class="c1"&gt;# http request trigger
&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;flow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;mitmproxy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTPFlow&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

        &lt;span class="n"&gt;method&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;flow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;
        &lt;span class="n"&gt;url&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;flow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;

        &lt;span class="c1"&gt;# Firebase upload domain
&lt;/span&gt;        &lt;span class="nf"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;firebasestorage.googleapis.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

            &lt;span class="c1"&gt;# handle upload request (POST)
&lt;/span&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;POST&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="c1"&gt;# content body
&lt;/span&gt;                &lt;span class="n"&gt;json_body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

                &lt;span class="c1"&gt;# record upload type (necessary for following PUT request)
&lt;/span&gt;                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;upload_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json_body&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;metadata&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

                &lt;span class="c1"&gt;# replace content length definition
&lt;/span&gt;                &lt;span class="n"&gt;flow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;x-goog-upload-content-length&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FAKEMOJI&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;st_size&lt;/span&gt;

            &lt;span class="c1"&gt;# handle sending file content (PUT)
&lt;/span&gt;            &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PUT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

                &lt;span class="c1"&gt;# only replace image if realmoji upload
&lt;/span&gt;                &lt;span class="nf"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;upload_type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;realmoji&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

                    &lt;span class="c1"&gt;# replace file content
&lt;/span&gt;                    &lt;span class="n"&gt;flow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;read_content&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FAKEMOJI&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;addons&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nc"&gt;Interceptor&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 script listens out for Firebase requests with a metadata type of “RealMoji” and then intercepts and manipulates them accordingly. The expected content size for the upload is altered and the content of the &lt;code&gt;PUT&lt;/code&gt; request is automatically replaced with the data of a local file: &lt;em&gt;fakemoji.jpg.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How to protect yourself from a MITM attack
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;If you use a company-managed phone which has custom certificates installed, there is a chance that they can see you making these sorts of requests.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In reality, performing this man-in-the-middle attack on someone else’s phone maliciously would be easier said than done. To get it working for me required configuration of the iPhone’s proxy settings as well as approval of certain device certificates which would not be practical for an attacker to do remotely; however, with a compromised device, something like this would be more than possible.&lt;/p&gt;

&lt;p&gt;If you use a company-managed phone which has custom certificates installed, there is a chance that they can see you making these sorts of requests. Obviously, this can’t be said for all companies but it can be hard to tell how “big brother” they are and how much of a grasp they have on your data and internet usage.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use a VPN.
&lt;/h3&gt;

&lt;p&gt;In order to prevent such attacks, you can try to protect your physical device from malicious fingers and for an added layer of security, &lt;a href="https://www.expressrefer.com/refer-a-friend/30-days-free?referrer_id=20340565&amp;amp;utm_campaign=referrals&amp;amp;utm_medium=copy_link&amp;amp;utm_source=referral_dashboard" rel="noopener noreferrer"&gt;use a VPN!&lt;/a&gt; (Shameless referral link here. Get 30 days free!). Yes, VPNs are &lt;strong&gt;not&lt;/strong&gt; perfect, and 90% of the time they are advertised to the general public incorrectly but for this instance, they are effective at masking your actions from prying network eyes.&lt;/p&gt;

&lt;p&gt;When I turn my VPN on and then send a RealMoji again, nothing is detected by Mitmproxy. I would include a screenshot to example but there is literally nothing to see. The requests which were shown before simply do not appear, as if the phone is not doing anything at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing thoughts
&lt;/h2&gt;

&lt;p&gt;This fun experiment shows how you can manipulate your own device HTTP traffic and see what requests apps are making behind the curtain. It is just another proof of how you can never trust the front end, even from a native app. As a developer, in order to have real security in place, you must install effective security measures on your back end to prevent misuse; otherwise, your production app is not much better than a sandbox environment!&lt;/p&gt;

</description>
      <category>python</category>
      <category>webdev</category>
      <category>programming</category>
      <category>api</category>
    </item>
    <item>
      <title>How to deploy an AWS Lambda Stack under a custom domain name</title>
      <dc:creator>Oscar</dc:creator>
      <pubDate>Thu, 11 Aug 2022 13:04:00 +0000</pubDate>
      <link>https://dev.to/ozcap/how-to-deploy-an-aws-lambda-stack-under-a-custom-domain-name-3dg6</link>
      <guid>https://dev.to/ozcap/how-to-deploy-an-aws-lambda-stack-under-a-custom-domain-name-3dg6</guid>
      <description>&lt;p&gt;Lambda functions are really cool. They are tiny cloud-based compute instances which get created and destroyed on each API call. They automatically scale and can be distributed across the globe and executed close to the user which can deliver very fast response times.&lt;br&gt;
In this guide, you will learn how to deploy a basic hello world function and how to link it to a domain name which you already own.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Amazon AWS Account&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html" rel="noopener noreferrer"&gt;AWS CLI tool&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install-windows.html" rel="noopener noreferrer"&gt;AWS SAM CLI tool&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-getting-started-hello-world.html" rel="noopener noreferrer"&gt;AWS Lambda function deployed to your AWS account&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A domain name with configurable DNS (I’m using Cloudflare)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For this tutorial, I will be deploying a simple lambda function to my custom domain: &lt;strong&gt;api-helloworld.oscars.dev&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1 - Create lambda function
&lt;/h2&gt;

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

&lt;span class="c"&gt;# initialise boilerplate AWS Lambda project&lt;/span&gt;
sam init
...
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; 1 - AWS Quick Start Templates
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; 1 - Hello World Example
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Use popular package &lt;span class="nb"&gt;type&lt;/span&gt;? &lt;span class="o"&gt;(&lt;/span&gt;Python and zip&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;y/N]: N
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; 10 - nodejs16.x
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; 1 - Zip
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; 2 - Hello World Example TypeScript
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Project name: hello-world



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

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

&lt;span class="c"&gt;# enter the directory and build the project&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;hello-world

&lt;span class="c"&gt;# build project&lt;/span&gt;
sam build &lt;span class="nt"&gt;--beta-features&lt;/span&gt;

&lt;span class="c"&gt;# deploy project to aws&lt;/span&gt;
sam deploy &lt;span class="nt"&gt;--guided&lt;/span&gt;
...
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Stack Name: hello-world
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; AWS Region: eu-west-1
&lt;span class="o"&gt;[&lt;/span&gt;...&lt;span class="s2"&gt;"yes"&lt;/span&gt; &lt;span class="k"&gt;for &lt;/span&gt;everything &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Your AWS Lambda function is now deployed to the cloud! You can see your new app under your console.&lt;/p&gt;

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

&lt;p&gt;Clicking on the app will then show you the URL from where the function can be invoked. In my case, I can directly execute my function with the URL below.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Step 2 - Configure Your Domain
&lt;/h2&gt;

&lt;p&gt;So we have a function which we can invoke from an API endpoint. great! But how do we link a domain to that?&lt;/p&gt;

&lt;h3&gt;
  
  
  Certificate Manager
&lt;/h3&gt;

&lt;p&gt;Head to Certificate Manager in your AWS console and then click on &lt;code&gt;request&lt;/code&gt; in the top-right to request a new certificate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Certificate type&lt;/strong&gt; &amp;gt; Select “Request public certificate” and click next.&lt;/p&gt;

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

&lt;p&gt;Input the domain name which you want (from a domain which you own), select DNS validation and then press &lt;code&gt;Request&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;After proceeding, you should be taken back to the certificates list where after refreshing, you should see your newly requested certificate with a “pending validation”. We’re not done yet!&lt;/p&gt;

&lt;p&gt;Click on the certificate to get to the detailed page. In the “Domains” section you should see two columns named &lt;code&gt;CNAME Name&lt;/code&gt; and &lt;code&gt;CNAME Value&lt;/code&gt; as below. Take a note of these values. you will need them later!&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;If your table is not displaying values like this, wait a little bit and then refresh the page. These entries normally get populated after a minute or so.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3 - Validate Certificate with DNS
&lt;/h2&gt;

&lt;p&gt;Go into your DNS settings for the domain you would like to configure. In my case I am using Cloudflare as it is free and it makes it really easy for me to manage my domains.&lt;/p&gt;

&lt;p&gt;Add a new CNAME record to your DNS and copy and paste the NAME and VALUE data from previously. After you hit confirm, you should then see a DNS entry which looks similar to this:&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Wait for a bit
&lt;/h3&gt;

&lt;p&gt;Go and make yourself a cup of tea as this will take a few minutes. It can depend on your DNS provider but it shouldn’t take longer than an hour. When the certificate is validated, you should see a green tick by the entry in Certificate manager.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Step 4 - Link your function
&lt;/h2&gt;

&lt;p&gt;When you’re all validated, go ahead and open up &lt;code&gt;API Gateway&lt;/code&gt; from your AWS dashboard.&lt;/p&gt;

&lt;p&gt;Along the left bar, go to &lt;code&gt;Custom domain names&lt;/code&gt; and then click &lt;code&gt;Create&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In Domain name, put the same domain you got the certificate for earlier. Leave everything else as default and select the corresponding certificate from the &lt;code&gt;ACM Certificate&lt;/code&gt; dropdown.&lt;/p&gt;

&lt;p&gt;Press &lt;code&gt;Create domain name&lt;/code&gt; and then under &lt;code&gt;API Mappings&lt;/code&gt;, create a new mapping with your function and the desired stage (Prod).&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Step 5 - Find the correct URL
&lt;/h2&gt;

&lt;p&gt;This bit caught me out when I first tried to set up a custom domain. The URL displayed on the function control panel isn’t actually the one which you use to forward the requests to.&lt;/p&gt;

&lt;p&gt;Open up a terminal and execute the following command to receive a response object with details about your domain.&lt;/p&gt;

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

aws apigateway get-domain-name &lt;span class="nt"&gt;--domain-name&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;YOUR DOMAIN&amp;gt;"&lt;/span&gt;
...
&lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"domainName"&lt;/span&gt;: &lt;span class="s2"&gt;"&amp;lt;DOMAIN_NAME&amp;gt;"&lt;/span&gt;,
    &lt;span class="s2"&gt;"certificateUploadDate"&lt;/span&gt;: &lt;span class="s2"&gt;"&amp;lt;Date&amp;gt;"&lt;/span&gt;,
    &lt;span class="s2"&gt;"regionalDomainName"&lt;/span&gt;: &lt;span class="s2"&gt;"&amp;lt;API_GATEWAY_ID&amp;gt;.execute-api.eu-west-1.amazonaws.com"&lt;/span&gt;,
    &lt;span class="s2"&gt;"regionalHostedZoneId"&lt;/span&gt;: &lt;span class="s2"&gt;"..."&lt;/span&gt;,
    &lt;span class="s2"&gt;"regionalCertificateArn"&lt;/span&gt;: &lt;span class="s2"&gt;"arn:aws:acm:eu-west-1:&amp;lt;ACCOUNT&amp;gt;:certificate/&amp;lt;CERT_ID&amp;gt;"&lt;/span&gt;,
    &lt;span class="s2"&gt;"endpointConfiguration"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;"types"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
            &lt;span class="s2"&gt;"REGIONAL"&lt;/span&gt;
        &lt;span class="o"&gt;]&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;,
        &lt;span class="s2"&gt;"domainNameStatus"&lt;/span&gt;: &lt;span class="s2"&gt;"AVAILABLE"&lt;/span&gt;,
&lt;span class="o"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;If your response looks like this, then it's looking good!&lt;br&gt;
Copy your “regionalDomainName”. You will need this for the final step.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6 - Link DNS Record
&lt;/h2&gt;

&lt;p&gt;Return to your DNS provider once again and another CNAME record (last one this time, I promise) with your &lt;strong&gt;subdomain as the name&lt;/strong&gt; (in my case api-helloworld) and your Target/Value as the regionalDomainName from earlier.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Finishing up
&lt;/h2&gt;

&lt;p&gt;Maybe make yourself another cup of tea while the DNS changes take effect. When the changes have applied, you should be able to execute your function via your domain!&lt;/p&gt;

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

</description>
      <category>aws</category>
      <category>typescript</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Learn Redux the right way: With Redux-toolkit</title>
      <dc:creator>Oscar</dc:creator>
      <pubDate>Mon, 28 Feb 2022 10:11:57 +0000</pubDate>
      <link>https://dev.to/ozcap/the-best-redux-tutorial-the-latest-way-57a0</link>
      <guid>https://dev.to/ozcap/the-best-redux-tutorial-the-latest-way-57a0</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Redux!&lt;/strong&gt; The word which you've been hearing over and over again. What is it? Why is it needed? Maybe you've glanced at the daunting Redux docs and thought "Nah, I'll learn that some other day". Well, today is that day and I guarantee that it will be easier than expected. &lt;/p&gt;

&lt;p&gt;Many existing Redux tutorials are outdated, but this guide will show you the latest, recommended way to implement the framework into your app using redux-toolkit. We will go over it in 5 simple steps. It is recommended that you follow along as we learn how to set up, read and write data to Redux. The rest, you can only learn through further experimenting.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Redux?
&lt;/h2&gt;

&lt;p&gt;Redux is a global state management system for your entire web/react app. Imagine a state which is shared by every component which can be read and updated at any level, on any page. No callback functions.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Set-up
&lt;/h2&gt;

&lt;p&gt;Lets get started. For this tutorial we be using NextJs and TypeScript. Start by initialising a project.&lt;br&gt;
&lt;code&gt;npx create-next-app@latest --ts&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;In the root of your project, install the dependencies &lt;strong&gt;react-redux&lt;/strong&gt; and &lt;strong&gt;@reduxjs/toolkit&lt;/strong&gt;.&lt;br&gt;
&lt;code&gt;npm i react-redux @reduxjs/toolkit&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;If you would rather just start playing around with the tutorial code now, the repository can be found &lt;a href="https://github.com/OZCAP/nextjs-redux-example"&gt;on my github&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  2. Create a slice
&lt;/h2&gt;

&lt;p&gt;Slices are the functions which define how a global state is managed. In a slice, we define the &lt;strong&gt;initial state&lt;/strong&gt; and also the &lt;strong&gt;reducers&lt;/strong&gt; which define how the data is manipulated. Create the file &lt;code&gt;src/reducers/FooReducer.tsx&lt;/code&gt; containing the code, below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// fooReducer.tsx

import { createSlice } from '@reduxjs/toolkit';

const initialValue = { name: "Nigel", age: 63 };

export const fooSlice = createSlice({
    name: 'foo',
    initialState: {value: initialValue},
    reducers: {
        changeAll: (state, action) =&amp;gt; {
            state.value = action.payload;
        },
        agePlusOne: (state, action) =&amp;gt; {
            state.value.age += 1;
        }
    }
})

export const { changeAll, agePlusOne } = fooSlice.actions;
export default fooSlice.reducer;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It looks like there is a lot going on, but it will become clearer. Trust me. Let's focus on what matters here.&lt;br&gt;
We have an &lt;code&gt;initialValue&lt;/code&gt; which defines the initial value of an object containing a 'name' and 'age' value.&lt;br&gt;
Under &lt;code&gt;reducers&lt;/code&gt; we have two special functions which show how the data can be manipulated. We may add as many of these reducer functions as we need.&lt;/p&gt;

&lt;p&gt;The function &lt;code&gt;changeAll&lt;/code&gt; takes in an object with new key values e.g. &lt;code&gt;{name: 'Bob', age: 44}&lt;/code&gt; and replaces the current key values.&lt;/p&gt;

&lt;p&gt;The function &lt;code&gt;getOlder&lt;/code&gt; takes no parameters, and increases the &lt;code&gt;age&lt;/code&gt; value by 1.&lt;/p&gt;
&lt;h2&gt;
  
  
  3. Set up Provider
&lt;/h2&gt;

&lt;p&gt;In order for the Redux state to be synchronised across the app, we must nest everything inside a &lt;code&gt;&amp;lt;Provider/&amp;gt;&lt;/code&gt; component. Copy the code below into &lt;code&gt;pages/_app.tsx&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;// _app.tsx
import type { AppProps } from 'next/app'

import { configureStore } from '@reduxjs/toolkit';
import { Provider } from 'react-redux';
import fooReducer from '../src/reducers/FooReducer'

const store = configureStore({
  reducer:  {
    foo: fooReducer,
  }
});

function MyApp({ Component, pageProps }: AppProps) {
  return (
  &amp;lt;Provider store={store}&amp;gt;
    &amp;lt;Component {...pageProps} /&amp;gt;
  &amp;lt;/Provider&amp;gt;
  )
}

export default MyApp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you are familiar with NextJs, you will know that &lt;code&gt;_app.tsx&lt;/code&gt; is the root component of the application. Any page loaded from &lt;code&gt;/pages&lt;/code&gt; is rendered inside &lt;code&gt;&amp;lt;Component {...pageProps} /&amp;gt;&lt;/code&gt; which means that all routes will always be within the &lt;code&gt;&amp;lt;Provider/&amp;gt;&lt;/code&gt; component, allowing access to the global state(s) defined by &lt;code&gt;store&lt;/code&gt; on any page.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Reading the global state
&lt;/h2&gt;

&lt;p&gt;Go ahead and copy the following code inside your &lt;code&gt;pages/index.tsx&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;// index.tsx
import type { NextPage } from 'next'
import { useSelector } from 'react-redux';

const Home: NextPage = () =&amp;gt; {
  const foo = useSelector(state =&amp;gt; state.foo.value);

  return (
    &amp;lt;main&amp;gt;
      &amp;lt;p&amp;gt;{foo.name}&amp;lt;/p&amp;gt;
      &amp;lt;p&amp;gt;{foo.age}&amp;lt;/p&amp;gt;
    &amp;lt;/main&amp;gt;
  )
}

export default Home
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When accessing the dev environment, we are now greeted with the text 'Nigel' and '63'. This is the initial state of the object we defined in &lt;code&gt;FooReducer.tsx&lt;/code&gt;!&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;useSelector()&lt;/code&gt; function this global state from the &lt;code&gt;store&lt;/code&gt; we set up in our &lt;code&gt;_app.tsx&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Writing to the global state
&lt;/h2&gt;

&lt;p&gt;Edit your index.tsx and add the following code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// index.tsx
//...
import { useDispatch } from 'react-redux';
import { agePlusOne } from '../src/reducers/FooReducer';

const Home: NextPage = () =&amp;gt; {
  //...
  const dispatch = useDispatch();

  return (
    &amp;lt;main&amp;gt;
      {foo.name}
      &amp;lt;button onClick={() =&amp;gt; {
        dispatch(agePlusOne(null));
      }}&amp;gt;Plus One&amp;lt;/button&amp;gt;
      &amp;lt;br /&amp;gt;
      {foo.age}
    &amp;lt;/main&amp;gt;
  )
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;useDispatch()&lt;/code&gt; is the function which is used to execute the functions outlined in our &lt;code&gt;FooReducer.tsx&lt;/code&gt;. Here, we have imported the &lt;code&gt;agePlusOne&lt;/code&gt; function which adds 1 to the current age value. When we click the button, the age will increase by 1. The function takes no arguments.&lt;/p&gt;

&lt;p&gt;If we want to do the same with the &lt;code&gt;changeAll&lt;/code&gt; function, we must import it like we did with the &lt;code&gt;agePlusOne&lt;/code&gt; function and call it with an argument of the new state that we want e.g. &lt;code&gt;dispatch(changeAll({name: 'Bob', age: 44}))&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;If you want to add more reducers, all you need to do is simply create additional components, for example &lt;code&gt;BarReducer.tsx&lt;/code&gt; and then include it in the &lt;code&gt;store&lt;/code&gt; constant defined in &lt;code&gt;_app.tsx&lt;/code&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;const store = configureStore({
  reducer:  {
    foo: fooReducer,
    bar: barReducer
  }
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After doing this, you can then reference &lt;code&gt;bar&lt;/code&gt;, as you did with &lt;code&gt;foo&lt;/code&gt; and have multiple global states!&lt;/p&gt;

</description>
      <category>redux</category>
      <category>typescript</category>
      <category>nextjs</category>
      <category>react</category>
    </item>
  </channel>
</rss>
