Introduction
Project Requirements
Login requirements.- Users should be able to log in using user social media Oauth provider google.
- Users should be able to log out, ending their session.
- Implement session management to keep users logged in across different pages.
- Users should be able to create (C in CRUD) new ToDo items. Each ToDo item should have a name, createdData, and updated Date.
- Users should be able to view or read (R in CRUD) ToDo items.
- Users should be able to update (U in CRUD) the name.
- Users should be able to delete (D in CRUD) ToDo items.
App Models
We will have 2 models in our app - User and ToDo.1model User {
2 id Int @id @default(autoincrement())
3 email String @unique
4 password String
5 username String? // Optional field
6 createdAt DateTime @default(now())
7 updatedAt DateTime @updatedAt
8 todos ToDo[] // Relationship to ToDo model
9 }
10
11 model ToDo {
12 id Int @id @default(autoincrement())
13 name String
14 status String @default("new")
15 createdAt DateTime @default(now())
16 updatedAt DateTime @updatedAt
17 userId Int // Foreign key field
18 user User @relation(fields: [userId], references: [id])
19 }
Setting Up Project
Install VSCode at VS Code
Install NodeJS at NodeJS
Use below command to setup nextjs project and select "Yes" to all the prompts!npx create-next-app@latest
Then Launch the app with below command and you should see your app running on http://localhost:3000!
npm run dev
Understanding Folder Structure
Here are main files and folders in nextJS app- src - App routers, Layout.tsx, page.tsx and API routers are stored here
- public - Public assets are stored here
- package.json - Project dependencies are stored here
- tsconfig.json - Typescript configuration file
- next.config.mjs - NextJS configuration file
- tailwind.config.ts - Tailwind Config file
- postcss.config.mjs - Postcss Config file
- README.md - Your project purpose and guide
- .gitignore - Files that you want git to ignore are stored here
- .eslintrc.json - ESLint configuration is stored here
- components - We will store reusable components here
- server - We will store server actions and prisma methods here
- lib - We will store reusable functions here
Environments and Variable Management
Just create ./.env file or ./.env.local file to store variables. NextJS will load this file automatically. Also add .env file in .gitignore. Please note that git repo is automatically created for you when you created nextjs project! It is recommeded to use .env.local file as it is already added in .gitignoreWriting Backend
PostgressSql
Prisma
1npm install prisma @prisma/client
2npx prisma init
DATABASE_URL="postgresql://username:password@localhost:5432/mydatabase?schema=public"
1generator client {
2 provider = "prisma-client-js"
3}
4
5datasource db {
6 provider = "postgresql"
7 url = env("DATABASE_URL")
8}
9
10
11 model User {
12 id Int @id @default(autoincrement())
13 email String @unique
14 password String
15 username String? // Optional field
16 createdAt DateTime @default(now())
17 updatedAt DateTime @updatedAt
18 todos ToDo[] // Relationship to ToDo model
19 }
20
21 model ToDo {
22 id Int @id @default(autoincrement())
23 name String
24 status String @default("new")
25 createdAt DateTime @default(now())
26 updatedAt DateTime @updatedAt
27 userId Int // Foreign key field
28 user User @relation(fields: [userId], references: [id])
29 }
npx prisma migrate dev --name init
npx prisma generate
1//src/server/prisma.ts
2
3let prisma;
4
5if (process.env.NODE_ENV === 'production') {
6 prisma = new PrismaClient();
7} else {
8 // In development, use a singleton pattern to prevent multiple instances
9 if (!global.prisma) {
10 global.prisma = new PrismaClient();
11 }
12 prisma = global.prisma;
13}
14
15module.exports = prisma;
const prisma = require('./prisma');
Writing CRUD functions in Prisma
First we will create file to store our CRUD operations. Create file - src/server/dbactions.ts1// Import PrismaClient singleton
2import { ToDo, User } from "@prisma/client";
3import prisma from "./prisma";
4import { auth } from "@/auth";
5import { redirect } from "next/navigation";
6
7// Function to insert data into the User table
8export async function createUser(email: string = "[email protected]") {
9 const newUser = await prisma.user.create({
10 data: {
11 email: email,
12 password: "hashedpassword123",
13 username: "newuser", // Optional field
14 },
15 });
16 //console.log("User created:", newUser);
17}
18
19// Function to read data from the User table
20export async function getAllUsers() {
21 const users = await prisma.user.findMany();
22 //console.log("All users:", users);
23}
24export async function getToDo(status: string) {
25 const session = await auth();
26
27 if (session?.user?.id === undefined) {
28 throw new Error("Invalid userId");
29 }
30
31 const todos = await prisma.toDo.findMany({
32 where: {
33 userId: parseInt(session?.user?.id), // Filter by user ID
34 ...(status !== "all" && { status: status }), // Filter by status if it's not "all"
35 },
36 orderBy: {
37 updatedAt: "desc", // Sort by the 'updatedAt' field in descending order
38 },
39 });
40
41 return todos;
42
43 // const todos = await prisma.toDo.findMany({
44 // where: status !== "all" ? { status: status } : {},
45 // orderBy: {
46 // updatedAt: "desc", // Sort by the 'updatedBy' field in descending order
47 // },
48 // });
49 // console.log("To Do list:", todos);
50
51 // return todos;
52}
53
54// Function to save todo
55export async function saveToDo(
56 toDo: Omit<ToDo, "id" | "createdAt" | "updatedAt" | "userId" | "status">
57) {
58 const session = await auth();
59
60 if (typeof session?.user?.id !== "string") {
61 throw new Error("Invalid userId");
62 }
63
64 const newToDo = await prisma.toDo.create({
65 data: {
66 name: toDo.name,
67 userId: parseInt(session?.user?.id), // Link the ToDo to the user
68 },
69 });
70 //console.log("To Do created:", newToDo);
71}
72
73export async function editToDoItem(
74 id: number,
75 toDo: Omit<ToDo, "id" | "createdAt" | "userId" | "updatedAt" | "status">
76) {
77 const updatedToDo = await prisma.toDo.update({
78 where: {
79 id: id,
80 },
81 data: toDo,
82 });
83
84 //console.log("To Do created:", updatedToDo);
85}
86
87export async function deleteToDoItem(id: number) {
88 const deletedTodo = await prisma.toDo.delete({
89 where: {
90 id: id,
91 },
92 });
93}
94
95// Function to find a user by email
96export async function findUserByEmail(email: string) {
97 const user = await prisma.user.findUnique({
98 where: {
99 email: email,
100 },
101 });
102 //console.log("User found:", user);
103}
104
105export async function updateUser(email: string, newData: User) {
106 const updatedUser = await prisma.user.update({
107 where: {
108 email: email,
109 },
110 data: newData,
111 });
112 //console.log("User updated:", updatedUser);
113}
114
115// Function to delete a user by email
116export async function deleteUser(email: string) {
117 const deletedUser = await prisma.user.delete({
118 where: {
119 email: email,
120 },
121 });
122 //console.log("User deleted:", deletedUser);
123}
Server Actions
Now We can use server actions at src/server/serveractions.ts (using prisma) to interact with database.1"use server";
2
3import { revalidatePath } from "next/cache";
4import { redirect } from "next/navigation";
5import { deleteToDoItem, editToDoItem, saveToDo } from "@/server/dbactions";
6
7export async function handleFilterChange(status?: string) {
8 console.log("Server action called ", status);
9 //revalidatePath("/" + "?status=" + status);
10 redirect("/" + "?status=" + status);
11}
12
13export async function createToDo(formData: FormData) {
14 const name = formData.get("name");
15
16 if (typeof name !== "string" || name.trim() === "") {
17 throw new Error("Name is required and must be a non-empty string.");
18 }
19
20 const rawFormData = {
21 name: name.trim(),
22 };
23
24 await saveToDo(rawFormData);
25
26 // mutate data
27 // revalidate cache
28 revalidatePath("/");
29}
30
31export async function deleteToDo(id: number) {
32 await deleteToDoItem(id);
33 revalidatePath("/");
34}
35
36export async function editToDo({ id, name }: { id: number; name: string }) {
37 if (typeof name !== "string" || name.trim() === "") {
38 throw new Error("Name is required and must be a non-empty string.");
39 }
40
41 const rawToDoData = {
42 name: name.trim(),
43 };
44
45 await editToDoItem(id, rawToDoData);
46 // mutate data
47 // revalidate cache
48 revalidatePath("/");
49}
Authentication using AuthJS
Create Google Oauth Credentials at https://console.cloud.google.com/apis/credentials . Follow below steps to get Oauth credentials.- Click Add credentials and then select Oauth
- Add Authorized JavaScript origins origin as "http://localhost:3000
- Add Authorized Redirect Uri as "http://localhost:3000/api/auth/callback/google"
- Then click Create button. Now you can copy OAuth Client Id and OAuth Client Secret!
1GOOGLE_CLIENT_ID={GOOGLE_CLIENT_ID}
2GOOGLE_CLIENT_SECRET={GOOGLE_CLIENT_SECRET}
npm install next-auth@beta
npx auth secret
1import NextAuth from "next-auth";
2import Google from "next-auth/providers/google";
3import type { NextAuthConfig, Session } from "next-auth";
4import { createUser } from "./server/dbactions";
5
6export const { handlers, auth } = NextAuth({
7 providers: [
8 Google({
9 clientId: process.env.GOOGLE_CLIENT_ID,
10 clientSecret: process.env.GOOGLE_CLIENT_SECRET,
11 }),
12 ],
13
14 debug: process.env.NODE_ENV === "development",
15
16 callbacks: {
17 async signIn({ user, account, profile, email, credentials }) {
18
19 // Example: Find or create the user
20 let checkUser = await prisma.user.findUnique({
21 where: { email: user.email },
22 });
23
24 if (!checkUser) {
25 await createUser(user.email!);
26 }
27 return true;
28 },
29
30 async jwt({ user, token, account, profile }) {
31
32 if (user) {
33 // User object is available only on initial sign-in or sign-up
34 if (!token.id) {
35 // Fetch user from the database and set the custom ID
36
37 const dbUser = await prisma.user.findUnique({
38 where: { email: user.email },
39 });
40
41 if (dbUser) {
42 console.log("setting jwt token");
43 token.id = dbUser.id; // Set custom ID in the token
44 }
45 }
46 }
47
48 return token;
49 },
50
51 async session({ session, token }) {
52
53 session.user.id = token?.id!?.toString();
54 return session;
55 },
56 },
57} satisfies NextAuthConfig);
1import { handlers } from "@/auth" // Referring to the auth.ts we just created
2export const { GET, POST } = handlers
Frontend
Identification of Components and State
When building a Todo app, identifying components and their states is crucial for managing the application's UI and functionality.
Here is the list of components- AuthenticationProvider and Appbar: AuthenticationProvider component will wrap our app and provide authentication related features! Appbar component will show profile icon and sign in or sign out button.
- FilterToDo : Component that will allow user to filter to do items based on if it is completed or not!
- ToDoList - Component that will show all to do items
- ToDoForm - Component that allows user to create new to do Item
Authentication in Server Component
Update src/app/page.tsx with below code! This example shows how to check session in server component. We will create FilterToDo, ToDoList and ToDoForm components in upcoming sections.1import { auth } from "@/auth";
2import { SignInButton, SignInLink } from "@/components/Buttons";
3import FilterToDo from "@/components/FilterToDo";
4import ToDoForm from "@/components/ToDoForm";
5import ToDoList from "@/components/ToDoList";
6import { getToDo } from "@/server/dbactions";
7import Link from "next/link";
8import { redirect } from "next/navigation";
9
10export default async function Home({ searchParams }: { searchParams: any }) {
11 const session = await auth();
12
13 if (!session?.user)
14 return (
15 <>
16 Please <SignInLink /> to view your ToDo items!!
17 </>
18 );
19
20 let status = "all";
21 if (searchParams.status) status = searchParams.status;
22
23 let toDoItems = await getToDo(status);
24
25 return (
26 <main>
27 <FilterToDo />
28 <div className="flex gap-5 flex-col sm:flex-row">
29 <ToDoList toDoItems={toDoItems} />
30 <ToDoForm />
31 </div>
32 </main>
33 );
34}
Authentication in Client Component
In app/layout.tsx, add below code!1import type { Metadata } from "next";
2import { Inter } from "next/font/google";
3import "@/app/globals.css";
4import { auth } from "@/auth";
5import { AuthProvider } from "@/components/AuthProvider";
6import AppBar from "@/components/AppBar";
7
8const inter = Inter({ subsets: ["latin"] });
9
10export const metadata: Metadata = {
11 title: "Create Next App",
12 description: "Generated by create next app",
13};
14
15export default async function RootLayout({
16 children,
17}: Readonly<{
18 children: React.ReactNode;
19}>) {
20 const session = await auth();
21 return (
22 <html lang="en">
23 <body className="bg-slate-700 text-white px-10 min-h-[80vh]">
24 <AuthProvider session={session}>
25 <AppBar />
26 <div className="py-10 mx-auto max-w-[1000px] flex justify-center">
27 {children}
28 </div>
29 </AuthProvider>
30 </body>
31 </html>
32 );
33}
1"use client"; //tells Next.js to render this component on the client
2export { SessionProvider as AuthProvider } from "next-auth/react";
1"use client";
2
3import { useRouter } from "next/navigation";
4import React from "react";
5
6const SignInButton = () => {
7 let router = useRouter();
8 return (
9 <button onClick={() => router.push("/api/auth/signin")}>Sign In</button>
10 );
11};
12
13const SignInLink = () => {
14 let router = useRouter();
15 return (
16 <button
17 className="border-b-2 border-b-white "
18 onClick={() => router.push("/api/auth/signin")}
19 >
20 Sign In
21 </button>
22 );
23};
24
25export { SignInButton, SignInLink };
1"use client";
2
3import { signIn, signOut, useSession } from "next-auth/react";
4import Link from "next/link";
5import { useRouter } from "next/navigation";
6import React from "react";
7import { SignInButton } from "./Buttons";
8
9const AppBar = () => {
10 const { data: session } = useSession();
11 let router = useRouter();
12
13 return (
14 <div className="flex justify-end gap-3">
15 <Link href="/">Home</Link>
16 <Link href="/dashboard">Dashboard</Link>
17
18 {session?.user ? (
19 <button onClick={() => signOut({ redirect: true, callbackUrl: "/" })}>
20 Sign Out
21 </button>
22 ) : (
23 <SignInButton />
24 )}
25 </div>
26 );
27};
28
29export default AppBar;
FilterToDo Component
In this component (src/components/client/FilterToDo.tsx), we are filtering the to do items using server actions and using push method of router. Recommeded way is to use push method of router as it is client based approach.1"use client";
2import { handleFilterChange } from "@/server/serveractions";
3import { useRouter } from "next/navigation";
4import React, { useState } from "react";
5
6const FilterToDo = () => {
7 let router = useRouter();
8
9 return (
10 <div className="flex gap-5">
11 {/* <button onClick={() => handleFilterChange("all")}>Show All</button> */}
12
13 <button onClick={() => router.push("/?status=all")}>Show All</button>
14 <button onClick={() => handleFilterChange("new")}>Show New</button>
15 <button onClick={() => handleFilterChange("completed")}>
16 Show Completed
17 </button>
18 </div>
19 );
20};
21
22export default FilterToDo;
ToDo List Items
We will be using some icons from the react icons library so we need to install react-icons package.npm i react-icons
1"use client";
2import React, { useState } from "react";
3import { ToDo } from "@prisma/client";
4import { deleteToDo, editToDo } from "@/server/serveractions";
5import { RiDeleteBin6Line } from "react-icons/ri";
6import { FaEdit } from "react-icons/fa";
7import { ImSpinner2 } from "react-icons/im"; // Import a loading spinner icon
8
9const ToDoList = ({ toDoItems }: { toDoItems: ToDo[] }) => {
10 const [editingId, setEditingId] = useState<number | null>(null);
11 const [editValue, setEditValue] = useState<string>("");
12 const [loadingId, setLoadingId] = useState<number | null>(null); // Track loading state for edit
13 const [deletingId, setDeletingId] = useState<number | null>(null); // Track loading state for delete
14
15 const handleEditClick = (id: number, name: string) => {
16 setEditingId(id);
17 setEditValue(name);
18 };
19
20 const handleSaveClick = async (id: number) => {
21 setLoadingId(id); // Set loading state for the current ID
22 try {
23 await editToDo({
24 id,
25 name: editValue,
26 });
27 } finally {
28 setLoadingId(null); // Clear loading state
29 setEditingId(null); // Exit editing mode
30 }
31 };
32
33 const handleDeleteClick = async (id: number) => {
34 setDeletingId(id); // Set loading state for delete
35 try {
36 await deleteToDo(id);
37 } finally {
38 setDeletingId(null); // Clear loading state
39 }
40 };
41
42 const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
43 setEditValue(e.target.value);
44 };
45
46 return (
47 <div>
48 <h1 className="m-5">ToDo List</h1>
49 <ul className="leading-10 list-disc list-inside bg-gray-100 px-20 rounded-lg shadow-md">
50 {toDoItems.map((todo) => (
51 <li
52 key={todo.id}
53 className="text-gray-800 py-2 border-b border-gray-300 last:border-b-0 flex justify-between items-center"
54 >
55 {editingId === todo.id ? (
56 <input
57 type="text"
58 value={editValue}
59 onChange={handleChange}
60 className="p-2 border border-gray-300 rounded"
61 />
62 ) : (
63 <span>{todo.name}</span>
64 )}
65 <div className="flex space-x-2 items-center">
66 {editingId === todo.id ? (
67 <>
68 <button
69 className="text-green-500 hover:text-green-700 p-2 flex items-center justify-center relative"
70 onClick={() => handleSaveClick(todo.id)}
71 >
72 {loadingId === todo.id ? (
73 <ImSpinner2 className="animate-spin w-5 h-5" />
74 ) : (
75 "Save"
76 )}
77 </button>
78 </>
79 ) : (
80 <button
81 className="text-blue-500 hover:text-blue-700 p-2 flex items-center justify-center"
82 onClick={() => handleEditClick(todo.id, todo.name)}
83 >
84 <FaEdit className="w-5 h-5" />
85 </button>
86 )}
87 <button
88 className="text-red-500 hover:text-red-700 p-2 flex items-center justify-center relative"
89 onClick={() => handleDeleteClick(todo.id)}
90 >
91 {deletingId === todo.id ? (
92 <ImSpinner2 className="animate-spin w-5 h-5" />
93 ) : (
94 <RiDeleteBin6Line className="w-5 h-5" />
95 )}
96 </button>
97 </div>
98 </li>
99 ))}
100 </ul>
101 </div>
102 );
103};
104
105export default ToDoList;
ToDo Form
Create below file src/components/ToDoForm.tsx1import { createToDo } from "@/server/serveractions";
2import React from "react";
3
4const ToDoForm = () => {
5 return (
6 <div>
7 <h1 className="m-5">ToDo Form</h1>
8 <form
9 action={createToDo}
10 className="max-w-sm mx-auto p-4 bg-white shadow-md rounded-lg"
11 >
12 <div className="mb-4">
13 <label className="block text-gray-700 font-bold mb-2" htmlFor="name">
14 ToDo Name:
15 </label>
16 <input
17 className="w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 text-black"
18 type="text"
19 name="name"
20 id="name"
21 required
22 />
23 </div>
24 <button
25 type="submit"
26 className="w-full bg-blue-500 text-white font-bold py-2 px-4 rounded hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-opacity-50"
27 >
28 Add ToDo
29 </button>
30 </form>
31 </div>
32 );
33};
34
35export default ToDoForm;
1import { auth } from "@/auth";
2import { SignInLink } from "@/components/Buttons";
3
4export default async function Dashboard() {
5 const session = await auth();
6
7 if (!session?.user)
8 return (
9 <>
10 Please <SignInLink /> to view your Dashboard!
11 </>
12 );
13
14 return (
15 <div className="flex justify-center place-content-center shadow-xl border-1 rounded-md bg-slate-600 flex-col p-10">
16 <div>
17 <span>Hello</span>
18 <span className="px-2 text-2xl">{session?.user?.name},</span>
19 </div>
20 <div className="my-5">
21 Email : <span className="px-2">{session?.user?.email}</span>
22 </div>
23 </div>
24 );
25}
Going Live
Deploy to Vercel
Install dependency.npm install -g vercel
npm run build
1vercel login
2cd /path/to/your/nextjs-app
3vercel
Custom Domain
If you have a custom domain, you can add it to your Vercel project under the "Domains" tab.Checking Logs and Metrics
You can look at logs of your in app in vercel dashboard!Securing App
Sanitizing Forms
Sanitizing form inputs in NextJS is crucial for protecting your application from security vulnerabilities such as Cross-Site Scripting (XSS) and ensuring that the data submitted by users is clean and formatted correctly.
Install dompurify and validator packages!1npm install dompurify
2npm install validator
1'use client'
2import React, { useState } from 'react';
3import DOMPurify from 'dompurify';
4import validator from 'validator';
5
6const Form = () => {
7 const [input, setInput] = useState('');
8 const [email, setEmail] = useState('');
9 const [error, setError] = useState('');
10
11 const handleInputChange = (e) => {
12 const rawInput = e.target.value;
13
14 // Trim and remove special characters
15 const sanitizedInput = rawInput.trim().replace(/[^ws]/gi, '');
16 setInput(sanitizedInput);
17 };
18
19 const handleEmailChange = (e) => {
20 const rawEmail = e.target.value;
21
22 // Escape HTML and validate email
23 const sanitizedEmail = DOMPurify.sanitize(rawEmail);
24 if (validator.isEmail(sanitizedEmail)) {
25 setEmail(sanitizedEmail);
26 setError('');
27 } else {
28 setError('Invalid email address');
29 }
30 };
31
32 const handleSubmit = (e) => {
33 e.preventDefault();
34 // Proceed with the sanitized and validated input
35 console.log('Sanitized Input:', input);
36 console.log('Sanitized and Validated Email:', email);
37 };
38
39 return (
40 <form onSubmit={handleSubmit}>
41 <div>
42 <label>Text Input:</label>
43 <input
44 type="text"
45 value={input}
46 onChange={handleInputChange}
47 />
48 </div>
49 <div>
50 <label>Email:</label>
51 <input
52 type="email"
53 value={email}
54 onChange={handleEmailChange}
55 />
56 {error && <p style={{ color: 'red' }}>{error}</p>}
57 </div>
58 <button type="submit">Submit</button>
59 </form>
60 );
61};
62
63export default Form;
Stopping Bots using Captcha
Turnstile is a CAPTCHA alternative provided by Cloudflare, designed to verify that a user is human without requiring them to complete traditional CAPTCHA challenges. Integrating Turnstile into a React form involves several steps, including setting up Turnstile on the server-side and then integrating it into your React application.npm install @marsidev/react-turnstile
1'use client'
2
3 import React, { useState } from 'react';
4import Turnstile from '@marsidev/react-turnstile';
5
6const MyForm = () => {
7 const [captchaToken, setCaptchaToken] = useState('');
8
9 const handleSubmit = async (e) => {
10 e.preventDefault();
11
12 // Submit form data along with the Turnstile token
13 const formData = {
14 // Your form data
15 captchaToken,
16 };
17
18 // Example: POST formData to your server
19 const response = await fetch('/api/submit-form', {
20 method: 'POST',
21 headers: {
22 'Content-Type': 'application/json',
23 },
24 body: JSON.stringify(formData),
25 });
26
27 const result = await response.json();
28 console.log(result);
29 };
30
31 return (
32 <form onSubmit={handleSubmit}>
33 <div>
34 <label>Your Name:</label>
35 <input type="text" name="name" required />
36 </div>
37 <div>
38 <label>Your Email:</label>
39 <input type="email" name="email" required />
40 </div>
41
42 {/* Turnstile CAPTCHA */}
43 <Turnstile
44 sitekey="your-site-key-here"
45 onSuccess={(token) => setCaptchaToken(token)}
46 onError={() => setCaptchaToken('')}
47 />
48
49 <button type="submit" disabled={!captchaToken}>Submit</button>
50 </form>
51 );
52};
53
54export default MyForm;
Rate Limiting
Since we are not using any API in nextjs 14, we do not need to do rate limiting for them!
WAF - Web Application Firewall
A Web Application Firewall (WAF) works by filtering and monitoring HTTP/HTTPS traffic between a web application and the internet to protect against various web-based threats. WAFs inspect incoming HTTP/HTTPS requests to identify and block malicious traffic. They check requests against predefined rules or patterns to detect potentially harmful content. WAFs use signatures and patterns to identify known attack vectors, such as SQL injection strings or XSS payloads.
Some examples- Block requests containing SQL keywords such as SELECT, UNION, or DROP in URL parameters or request bodies.
- Block requests containing script tags (script) or JavaScript events (onerror, onclick) in user input.
- Block uploads of executable files (.exe, .php) or files larger than a specified size.
- Block requests using methods like TRACE or DELETE if not required by the application.
- Block known malicious IP addresses or allow only certain trusted IP ranges.
- Block requests with suspicious patterns, such as rapid request rates or known bot signatures.
- Allow a maximum of 100 requests per IP address per minute.
Scaling App
Using middleware
- Rate Limiting is usually implemented in middleware!
- Cache responses to reduce the load on your application and database by serving cached content for frequently accessed requests.
- Reduce the size of the response data sent to clients, improving load times and reducing bandwidth usage.
- Manage user authentication and authorization, ensuring that only authorized users can access certain resources.
- Log requests and responses for monitoring and debugging purposes.
- Protect your application from common security threats, such as cross-site scripting (XSS) and clickjacking, which can impact performance and reliability. CSP headers can be injected in server components.
Horizontal Scaling
- Containerization and K8s
- Load Balancing
- Auto Scaling
Vertical Scaling
- Upgrade Instance Size
- Database Optimization
Caching
- In-Memory Caching - Redis/Memcached
- HTTP Caching - Set appropriate cache headers (e.g., Cache-Control, Expires) to enable client-side caching of static resources
- Content Delivery Network (CDN)
Asynchronous Processing
- Background jobs - Use job queues like Bull or Kue to handle background tasks asynchronously. Implement worker processes or microservices to handle long-running tasks separately from your main application.
- Use message brokers (RabbitMQ/Kafka) to manage communication between different components of your application and handle distributed tasks.
Monitoring and logging
- Use tools like Prometheus, Grafana, or New Relic to monitor application performance, resource usage, and response times.
- Use centralized logging systems like ELK Stack (Elasticsearch, Logstash, Kibana) or Loki to aggregate and analyze logs from multiple instances.