Week 13.1 Notes

Download as pdf or txt
Download as pdf or txt
You are on page 1of 21

week 13.

1 notes
Week 13.1
Building Medium
Up until now, our discussions have primarily revolved around theoretical concepts.
In this lecture, Harkirat takes a practical approach by guiding us through the hands-
on process of building a Medium like application
We'll be applying the knowledge we've gained so far, specifically focusing on
implementing the frontend using React and the backend using Cloudflare Workers —
creating a modern fullstack application.

While there are no specific notes provided for this section, a


mini guide is outlined below to assist you in navigating through
the process of building the application. Therefore, it is strongly
advised to actively follow along during the lecture for a hands-
on learning experience.

Building MediumStep 1 — The stackStep 2 - Initialize the backendStep 3 - Initialize


handlersSolutionStep 4 - Initialize DB (prisma)1. Get your connection url from
neon.db or aieven.tech2. Get connection pool URL from Prisma accelerate3.
Initialize prisma in your project4. Initialize the schema5. Migrate your database6.
Generate the prisma client7. Add the accelerate extension8. Initialize the prisma
clientStep 5 - Create routes1. Simple Signup routeSolution2. Add JWT to signup
routeSolution3. Add a signin routeSolutionStep 6 - Middlewares1. Limiting the
middleware2. Writing the middleware3. Confirm that the user is able to access
authenticated routesStep 7 - Blog routes and better routingBetter routingBlog
routes1. Create the route to initialize a blog/post2. Create the route to update
blog3. Create the route to get a blogStep 8 - Understanding the typesBindingsIn
our case, we need 2 env variables -VariablesStep 9 - Deploy your appUpdate the
env variables from cloudflare dashboardStep 10 - Zod validationStep 11 - Initialise

week 13.1 notes 1


commonStep 12 - Import zod in backendSolutionStep 13 - Init the FE projectStep
14 - Add react-router-dom

Step 1 — The stack


We’ll be building medium in the following stack

1. React in the frontend

2. Cloudflare workers in the backend

3. zod as the validation library, type inference for the frontend types

4. Typescript as the language

5. Prisma as the ORM, with connection pooling

6. Postgres as the database

7. jwt for authentication (Cookies approach explained in the end as well)

Step 2 - Initialize the backend


Whenever you’re building a project, usually the first thing you should do is
initialise the project’s backend.

Create a new folder called medium

mkdir medium
cd medium

Initialize a hono based cloudflare worker app

npm create hono@latest

Target directory › backend

Which template do you want to use? - cloudflare-workers

Do you want to install project dependencies? … yes


Which package manager do you want to use? › npm (or yarn or bun, doesnt

week 13.1 notes 2


matter)
💡
Reference https://hono.dev/top

Step 3 - Initialize handlers


To begin with, our backend will have 4 routes

1. POST /api/v1/signup

2. POST /api/v1/signin

3. POST /api/v1/blog

4. PUT /api/v1/blog

5. GET /api/v1/blog/:id

💡
https://hono.dev/api/routing

Solution

Step 4 - Initialize DB (prisma)


1. Get your connection url from neon.db or aieven.tech

postgres://avnadmin:password@host/db

2. Get connection pool URL from Prisma accelerate


https://www.prisma.io/data-platform/accelerate

prisma://accelerate.prisma-data.net/?api_key=eyJhbGciOiJIUzI1
NiIsInR5cCI6IkpXVCJ9.eyJhcGlfa2V5IjoiNTM2M2U5ZjEtNmNjMS00MWNk
LWJiZTctN2U4NzFmMGFhZjJmIiwidGVuYW50X2lkIjoiY2I5OTE2NDk0MzFkN
WZmZWRmNmFiYzViMGFlOTIwYzFhZDRjMGY5MTg1ZjZiNDY0OTc3MzgyN2IyMz

week 13.1 notes 3


Y2OWIwMiIsImludGVybmFsX3NlY3JldCI6Ijc0NjE4YWY2LTA4NmItNDM0OC0
4MzIxLWMyMmY2NDEwOTExNyJ9.HXnE3vZjf8YH71uOollsvrV-TSe41770FPG
_O8IaVgs

3. Initialize prisma in your project


Make sure you are in the backend folder

npm i prisma
npx prisma init

Replace DATABASE_URL in .env

DATABASE_URL="postgres://avnadmin:password@host/db"

Add DATABASE_URL as the connection pool url in wrangler.toml

name = "backend"
compatibility_date = "2023-12-01"

[vars]
DATABASE_URL = "prisma://accelerate.prisma-data.net/?api_key=
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhcGlfa2V5IjoiNTM2M2U5
ZjEtNmNjMS00MWNkLWJiZTctN2U4NzFmMGFhZjJmIiwidGVuYW50X2lkIjoiY
2I5OTE2NDk0MzFkNWZmZWRmNmFiYzViMGFlOTIwYzFhZDRjMGY5MTg1ZjZiND
Y0OTc3MzgyN2IyMzY2OWIwMiIsImludGVybmFsX3NlY3JldCI6Ijc0NjE4YWY
2LTA4NmItNDM0OC04MzIxLWMyMmY2NDEwOTExNyJ9.HXnE3vZjf8YH71uOoll
svrV-TSe41770FPG_O8IaVgs"

💡
You should not have your prod URL committed either in .env or in wrangler.toml to
github

week 13.1 notes 4


wranger.toml should have a dev/local DB url
.env should be in .gitignore

4. Initialize the schema

generator client {
provider = "prisma-client-js"
}

datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}

model User {
id String @id @default(uuid())
email String @unique
name String?
password String
posts Post[]
}

model Post {
id String @id @default(uuid())
title String
content String
published Boolean @default(false)
author User @relation(fields: [authorId], reference
s: [id])
authorId String
}

5. Migrate your database

week 13.1 notes 5


npx prisma migrate dev --name init_schema

💡
You might face issues here, try changing your wifi if that happens

6. Generate the prisma client

npx prisma generate --no-engine

7. Add the accelerate extension

npm install @prisma/extension-accelerate

8. Initialize the prisma client

import { PrismaClient } from '@prisma/client/edge'


import { withAccelerate } from '@prisma/extension-accelerate'

const prisma = new PrismaClient({


datasourceUrl: env.DATABASE_URL,
}).$extends(withAccelerate())

Step 5 - Create routes


1. Simple Signup route
Add the logic to insert data to the DB, and if an error is thrown, tell the user about
it

Solution

week 13.1 notes 6


💡
To get the right types on c.env , when initializing the Hono app, pass the types of
env as a generic

const app = new Hono<{


Bindings: {
DATABASE_URL: string
}
}>();

💡
Ideally you shouldn’t store passwords in plaintext. You should hash before storing
them. More details on how you can do that -

https://community.cloudflare.com/t/options-for-password-
hashing/138077https://developers.cloudflare.com/workers/runtime-apis/web-
crypto/

2. Add JWT to signup route


Also add the logic to return the user a jwt when their user id encoded.
This would also involve adding a new env variable
JWT_SECRET to wrangler.toml
💡
Use jwt provided by hono - https://hono.dev/helpers/jwt

Solution

3. Add a signin route


Solution

Step 6 - Middlewares
Creating a middleware in hono is well documented -
https://hono.dev/guides/middleware

week 13.1 notes 7


1. Limiting the middleware
To restrict a middleware to certain routes, you can use the following -

app.use('/message/*', async (c, next) => {


await next()
})

In our case, the following routes need to be protected -

app.get('/api/v1/blog/:id', (c) => {})

app.post('/api/v1/blog', (c) => {})

app.put('/api/v1/blog', (c) => {})

So we can add a top level middleware

app.use('/api/v1/blog/*', async (c, next) => {


await next()
})

2. Writing the middleware


Write the logic that extracts the user id and passes it over to the main route.

How to pass data from middleware to the route handler?

week 13.1 notes 8


How to make sure the types of variables that are being passed is correct?

Solution

3. Confirm that the user is able to access authenticated routes

app.post('/api/v1/blog', (c) => {


console.log(c.get('userId'));
return c.text('signin route')
})

Send the Header from Postman and ensure that the user id gets logged on the
server

week 13.1 notes 9


💡
If you want, you can extract the prisma variable in a global middleware that set’s it
on the context variable

app.use(”*”, (c) => {


const prisma = new PrismaClient({
datasourceUrl: c.env.DATABASE_URL,
}).$extends(withAccelerate());
c.set(”prisma”, prisma);
})

Ref https://stackoverflow.com/questions/75554786/use-cloudflare-worker-env-
outside-fetch-scope

Step 7 - Blog routes and better routing


Better routing
https://hono.dev/api/routing#grouping

Hono let’s you group routes together so you can have a cleaner file structure.
Create two new files -
routes/user.ts

routes/blog.ts

and push the user routes to


user.ts

index.ts

user.ts

Blog routes

1. Create the route to initialize a blog/post


Solution

2. Create the route to update blog

week 13.1 notes 10


Solution

3. Create the route to get a blog


Solution

Try to hit the routes via POSTMAN and ensure they work as expected

Step 8 - Understanding the types


Bindings
https://hono.dev/getting-started/cloudflare-workers#bindings

week 13.1 notes 11


In our case, we need 2 env variables -
JWT_SECRET
DATABASE_URL

week 13.1 notes 12


Variables
https://hono.dev/api/context#var
If you want to get and set values on the context of the request, you can use c.get

and c.set

You need to make typescript aware of the variables that you will be setting on the
context.

week 13.1 notes 13


💡
You can also create a middleware that sets prisma in the context so you don’t need
to initialise it in the function body again and again

Step 9 - Deploy your app

npm run deploy

💡
Make sure you have logged in the cloudflare cli using npx wrangler login

Update the env variables from cloudflare dashboard

week 13.1 notes 14


Test your production URL in postman, make sure it works

Step 10 - Zod validation


If you’ve gone through the video Cohort 1 - Deploying npm packages, Intro to Monorepos ,
you’ll notice we introduced type inference in Zod
https://zod.dev/?id=type-inference
This let’s you get types from runtime zod variables that you can use on your
frontend

week 13.1 notes 15


We will divide our project into 3 parts

1. Backend

2. Frontend

3. common

commonwill contain all the things that frontend and backend want to share.
We will make
common an independent npm module for now.

Eventually, we will see how


monorepos make it easier to have multiple packages sharing code in the same repo

Step 11 - Initialise common


1. Create a new folder called common and initialize an empty ts project in it

mkdir common

week 13.1 notes 16


cd common
npm init -y
npx tsc --init

1. Update tsconfig.json

"rootDir": "./src",
"outDir": "./dist",
"declaration": true,

1. Sign up/login to npmjs.org

2. Run npm login

3. Update the name in package.json to be in your own npm namespace, Update


main to be dist/index.js

{
"name": "@100xdevs/common-app",
"version": "1.0.0",
"description": "",
"main": "dist/index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}

1. Add src to .npmignore

2. Install zod

week 13.1 notes 17


npm i zod

1. Put all types in src/index.ts

a. signupInput / SignupInput

b. signinInput / SigninInput

c. createPostInput / CreatePostInput

d. updatePostInput / UpdatePostInput

Solution

1. tsc -b to generate the output

2. Publish to npm

npm publish --access public

1. Explore your package on npmjs

Step 12 - Import zod in backend


1. Go to the backend folder

cd backend

1. Install the package you published to npm

npm i your_package_name

1. Explore the package

cd node_modules/your_package_name

1. Update the routes to do zod validation on them

Solution

week 13.1 notes 18


Step 13 - Init the FE project
1. Initialise a react app

npm create vite@latest

1. Initialise tailwind

https://tailwindcss.com/docs/guides/vite

npm install -D tailwindcss postcss autoprefixer


npx tailwindcss init -p

1. Update tailwind.config.js

/** @type {import('tailwindcss').Config} */


export default {
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {},
},
plugins: [],
}

1. Update index.css

@tailwind base;

week 13.1 notes 19


@tailwind components;
@tailwind utilities;

1. Empty up App.css

2. Install your package

npm i your_package

1. Run the project locally

npm run dev

Step 14 - Add react-router-dom


1. Add react-router-dom

npm i react-router-dom

2. Add routing (ensure you create the Signup, Signin and Blog components)

import { BrowserRouter, Route, Routes } from 'react-router-do


m'
import { Signup } from './pages/Signup'
import { Signin } from './pages/Signin'
import { Blog } from './pages/Blog'

function App() {

return (
<>
<BrowserRouter>
<Routes>
<Route path="/signup" element={<Signup />} />

week 13.1 notes 20


<Route path="/signin" element={<Signin />} />
<Route path="/blog/:id" element={<Blog />} />
</Routes>
</BrowserRouter>
</>
)
}

export default App

3. Make sure you can import types from your_package

week 13.1 notes 21

You might also like