Welcome to our quest to build the most over-engineered, enterprise-grade ToDo list known to humankind — because simple local storage just isn’t fun enough, right?
We’re going to build a fully serverless ToDo app using Next.js, React, NextAuth.js, AWS Lambda, and DynamoDB.
Yes, it’s just a list of todos.
Yes, we’re using enough cloud tech to run NASA.
🧱 Step 1: Spin up the Next.js app
First things first, let’s create the Next.js app like all proper developers do — by copying from the docs and pretending we wrote it ourselves.
|
|
If you don’t want TypeScript, remove the --typescript
flag, but I promise it’s worth the tiny bit of pain.
Now install NextAuth.js, our trusty authentication wizard:
|
|
🛂 Step 2: Set up NextAuth with GitHub login
We’re gonna let users log in using GitHub, because password forms are sooo 2009.
Create this file:
|
|
And drop in this magical incantation:
|
|
Now go set up a GitHub OAuth app here: https://github.com/settings/developers
Use http://localhost:3000
as your homepage and callback URL (don’t forget to click Save, or you’ll wonder why nothing works for 20 minutes).
Then, create a .env.local
file and add:
|
|
🪄 Step 3: Wrap your app in a SessionProvider
To give your whole app access to authentication data, we wrap everything in the magical <SessionProvider>
.
Open up:
|
|
And wrap it like a burrito:
|
|
Boom — now you’ve got access to the session from anywhere. Kinda like global state, but legal.
😎 Step 4: Add a login/logout button
Now let’s see this baby in action.
Open up pages/index.tsx
and paste this in:
|
|
Now run your dev server:
|
|
Go to http://localhost:3000, and boom 💥
You now have a login button that opens GitHub, and a logout button that kicks the user out. We’re cooking now.
🧪 Sanity Check: Is it working?
If you can:
- Login with GitHub
- See your name show up
- Logout and go back to sad mode
Then congrats — you’ve got auth working!
If not, double-check your .env.local
values. Or scream into the void — either helps.
🗝️ Key Ideas
Key Idea | Summary |
---|---|
NextAuth.js | Handles all our auth flow |
GitHub OAuth | Lets users log in securely |
SessionProvider | Shares auth session across app |
Protected session-aware pages | React components use useSession |
Next up: Lambda + API Gateway | Backend magic time |
Part 2: AWS Lambda + Serverless Framework Setup
In Part 1, we got authentication up and running with Next.js and NextAuth.js — like responsible devs.
Now it’s time to call upon the power of the cloud and deploy some backend code using AWS Lambda via the Serverless Framework.
We’re gonna write our own API using Lambdas like the absolute legends we are.
📦 Step 1: Set up the backend folder
Let’s keep things tidy.
Inside the root of your project, make a sibling folder for the backend:
|
|
We’re going to install all the things we need to conjure cloud functions:
|
|
We’ll use:
serverless
– deploys code to AWS Lambdaserverless-offline
– lets us test locallytypescript
– because we love ourselvesesbuild
– because life’s too short for slow builds
⚙️ Step 2: Configure Serverless Framework
Create a file called serverless.yml
in the backend/
folder.
Here’s the basic config to start:
|
|
We just declared two functions: getTodos
and addTodo
.
They’ll be accessible over HTTP via API Gateway. Free REST API, baby.
🧠 Step 3: Add your function handlers
Let’s make a folder for your Lambdas:
|
|
Create getTodos.ts
:
|
|
And addTodo.ts
:
|
|
They’re fake for now — no database yet — but we’ll fix that in Part 4. Pinky swear.
🧪 Step 4: Test locally with serverless-offline
Before you deploy your code to the skies, let’s test locally.
Update your package.json
scripts:
|
|
Then run:
|
|
Now you should see:
|
|
Test the GET route in your browser.
For the POST route, you can use Postman or cURL:
|
|
You should get back your new todo. 🍍
☁️ Step 5: Deploy to AWS
Make sure you’ve configured your AWS credentials (with aws configure
or environment variables).
Then deploy with:
|
|
If all goes well, you’ll get public URLs for your endpoints like:
|
|
Now your API is officially on the cloud.
Cue dramatic orchestral music. 🎻✨
🗝️ Key Ideas
Key Idea | Summary |
---|---|
Serverless Framework | Simplifies Lambda + API Gateway setup |
Local dev with serverless-offline | Lets you test functions before deploying |
HTTP Functions | Create RESTful endpoints with YAML config |
AWS Lambda | Our serverless backend brain |
API Gateway | Exposes our Lambdas over the web |
Next up: Auth protection | Because not everyone deserves your todos |
Part 3: Protecting AWS Lambda Routes with NextAuth JWTs
Alright, welcome to Part 3 of our cloud-powered ToDo adventure!
So far, we’ve got:
- ✅ A frontend with Next.js, React, and NextAuth
- ✅ A backend with AWS Lambda and API Gateway
- 🚨 But absolutely zero protection on our Lambda routes
If someone finds your API endpoint right now, they could post “Rickroll” todos all day long.
Not on our watch. Let’s lock this thing down using NextAuth JWTs.
🧠 Quick Overview: What’s the plan?
- Configure NextAuth to use JWT sessions
- Send the JWT in the Authorization header when calling Lambda
- In the Lambda function, verify the JWT using the secret
- Only allow access if the token is valid
Sounds fancy. It is. But also not too hard.
Let’s do it. 🛡️
🔐 Step 1: Use JWTs in NextAuth
By default, NextAuth supports JWTs. But we’re going to be explicit, because we’re classy like that.
Update your [...nextauth].ts
:
|
|
🧾 Step 2: Send the JWT to your Lambda
Inside your frontend, get the token using getToken
from NextAuth.
Install the helper:
|
|
Then in your component or utility:
|
|
Pro tip: If you’re using getServerSideProps
, you can call getToken({ req })
directly from the context.
🔍 Step 3: Verify JWT in Lambda
Now let’s head over to the backend.
Install the JWT package in your backend/
:
|
|
Then update your function to verify the token:
|
|
Now your API is locked tighter than grandma’s cookie jar. 🍪🔒
🧪 Test It All Together
- Log in with GitHub in your frontend
- Call the secure Lambda route with your JWT in the header
- See your sweet, sweet todos coming back (or errors if you mess up — which is part of the fun)
If you get a 401 or 403, double-check:
- The token is being sent
- You’re using the same
NEXTAUTH_SECRET
in both frontend and backend - You didn’t copy-paste something from Stack Overflow without reading 😬
🧮 Bonus: DRY your auth logic
Create a verifyJwt.ts
helper:
|
|
Use it in any function. Your code just got 37% cleaner. ✨
🗝️ Key Ideas
Key Idea | Summary |
---|---|
JWT session strategy | Stores session data in a signed token |
Token sent via Authorization | Protects your Lambda endpoints |
Token verified inside Lambda | Keeps your backend secure |
Secret shared across services | MUST be the same for both ends |
Next: store todos in DynamoDB | It’s DB time, baby 🗃️ |
Part 4: Storing Todos in DynamoDB
So far we’ve built:
- ✅ A slick frontend with Next.js and NextAuth
- ✅ A backend using AWS Lambda
- ✅ Authentication with secure JWT-based access
But right now, our todos are living in memory.
Which means the moment you refresh, poof! 💨 They vanish into the ether like your dreams of becoming a DJ.
Time to give these todos a home — a safe, warm place where they can live forever (or until you delete them).
Let’s bring in DynamoDB, the serverless NoSQL database from AWS.
🧱 Step 1: Create a DynamoDB Table
Go to the AWS Console, search for DynamoDB, and click “Create table”.
Use the following settings:
- Table name:
todos
- Partition key:
userId
(type: String) - Sort key:
id
(type: String)
Keep the rest of the settings as defaults, and hit Create.
Boom. You’ve now got a place to dump todos like a digital hoarder. 🧻
🧪 Step 2: Add AWS SDK to your backend
Inside the backend/
folder, install the AWS SDK v3:
|
|
📦 Step 3: Create a reusable DB client
Let’s set up a DynamoDB DocumentClient that doesn’t make you cry every time you use it.
|
|
Simple. Clean. Fancy.
🧾 Step 4: Store todos in addTodo
Update addTodo.ts
to store the todo in DynamoDB:
|
|
We extract the sub
field from the JWT (which is usually the user ID), then store the todo under their ID.
📬 Step 5: Read todos in getTodos
Now update your getTodos.ts
to pull the user’s todos from DynamoDB:
|
|
You’re now officially storing and fetching todos per user like a total pro. 😎
🛑 Step 6: Don’t forget permissions!
Your Lambda functions need permission to read and write from DynamoDB.
Update your serverless.yml
:
|
|
Now redeploy:
|
|
✅ Test it out
- Log in with GitHub
- Use your frontend to add a todo (we’ll hook this up in Part 5!)
- Check DynamoDB — your new todo should be sitting there, feeling proud of itself
🗝️ Key Ideas
Key Idea | Summary |
---|---|
DynamoDB Table | Stores todos with userId as partition key |
AWS SDK v3 | Used to interact with DynamoDB |
Token-based auth | Securely associates todos with users |
PutCommand / QueryCommand | Store and retrieve user-specific items |
Next up: UI integration | Time to make it pretty and interactive! 🎨 |
Part 5: Wiring Up the UI with AWS Lambda
Alrighty, welcome to Part 5, the part where we finally connect the dots and bring everything to life.
We’ve got:
- ✅ Auth
- ✅ Lambda functions
- ✅ Secure access with JWTs
- ✅ A database (DynamoDB!) that’s more persistent than a clingy ex
Now it’s time to hook up the UI — meaning:
- Display todos pulled from the Lambda API
- Let the user add a todo
- Show loading/error states like pros
- And of course, keep it ✨ cute ✨
🧠 Step 1: Fetch todos from the API
We’re gonna hit the /todos
endpoint and include the JWT.
Let’s make a helper.
🛠️ utils/api.ts
|
|
Set the URL in your .env.local
:
|
|
💻 Step 2: Build the UI
Let’s build a cute little dashboard that shows todos and lets you add more.
🧑🎨 pages/index.tsx
|
|
This is a bit basic, but hey, it works!
You:
- See todos
- Add todos
- Use serverless APIs like a boss
🪄 Step 3: Make your session include the token (optional)
NextAuth doesn’t expose the token in the session by default on the client. If you want to include it in the session for easy access, you can update the session callback like this:
In [...nextauth].ts
:
|
|
Now you can get session.token
directly on the frontend.
✅ That’s a Wrap!
You’ve now got a real, live, working ToDo app that:
- Authenticates users via GitHub
- Stores data in DynamoDB
- Uses AWS Lambda for all backend logic
- Is serverless and extremely scalable
Also, you did all of this with basically zero infrastructure to maintain. Just some code, a little YAML, and pure cloud power. ☁️💪
🧠 What’s Next?
If you want to keep going, you can:
- Add a delete button
- Toggle
done
status with a click - Add filtering (All, Active, Done)
- Make it pretty with Tailwind or shadcn/ui
- Deploy the frontend to Vercel
Or you can just take a victory nap. You earned it. 😴
🗝️ Key Ideas
Key Idea | Summary |
---|---|
fetchTodos / addTodo | Custom fetch helpers using JWTs |
Session-aware frontend | useSession gives access to logged-in state |
Secure API interaction | Token sent via headers to protected Lambda |
Real-time UI | React state updates after API calls |
Serverless frontend + backend | Fully cloud-native productivity stack |
Part 6: Polishing, Deploying, and Final Touches
Congratulations, you magnificent cloud creature! 🥳
You’ve made it to the grand finale of our totally over-the-top ToDo app adventure.
At this point, you’ve got:
- ✅ Auth via NextAuth.js
- ✅ Backend API in AWS Lambda
- ✅ Todos in DynamoDB
- ✅ A shiny React-based frontend calling secure endpoints
But we’re not done yet.
This part is all about polishing the app, deploying it to the cloud, and adding those final sprinkles of awesomeness to make it feel like a real product, not just a weekend hack.
Let’s make it sparkle. ✨
🚀 Step 1: Deploy the Frontend to Vercel
First, push your frontend/
code to a GitHub repo.
Then go to https://vercel.com, sign in, and click “New Project”.
Connect your repo and follow the setup prompts.
Make sure to add these Environment Variables in the Vercel dashboard:
|
|
Then hit deploy.
Boom. Your app is live on the internet.
You’re officially in production, baby. 🕺
💅 Step 2: Add Tailwind for Styling
Install Tailwind CSS in your Next.js project:
|
|
Update tailwind.config.js
:
|
|
Update globals.css
:
|
|
Now start sprinkling Tailwind classes all over your UI like:
|
|
It’s like instant pretty. 💅
❌ Step 3: Add Delete Functionality
Your users should be able to delete todos — it’s 2025, after all.
Add a DELETE route in your Lambda config:
|
|
Then the function:
|
|
Then in the frontend:
|
|
Now add a 🗑️ button in your UI and you’re good to go.
⚙️ Step 4: Automate Deployments (Optional)
Wanna go full pro mode?
- Use GitHub Actions or Vercel’s Git Integration to auto-deploy on push
- Add a CI workflow that lints your code
- Create a
serverless deploy
GitHub Action for backend updates
Now your app updates itself while you eat nachos. 🤌
🌑 Bonus: Dark Mode? Animations? AI?
Feeling spicy?
- Add dark mode using Tailwind’s
dark
class - Use Framer Motion to animate todo entries
- Hook up ChatGPT to auto-write todos for you (because why not?)
Sky’s the limit — or rather, cloud’s the limit ☁️
🎉 You Made It!
You’ve officially built a full-stack, serverless, authenticated ToDo app using:
- Next.js for frontend
- React for UI
- NextAuth.js for secure login
- AWS Lambda + API Gateway for backend
- DynamoDB for storage
- Vercel for deployment
You should feel proud. Like, real proud. Take a selfie with your app. Post it. Brag a little. You earned it. 😎
🗝️ Key Ideas
Key Idea | Summary |
---|---|
Vercel Deployment | One-click frontend hosting with env vars |
Tailwind CSS | Instant pretty with utility classes |
Delete Function | Securely remove todos from DynamoDB |
CI/CD | Automate deployment and backend updates |
Project complete! | Ship it, share it, flex it 💪 |