Skip to content

Commit fc51ddf

Browse files
committed
First commit
1 parent 19d2da9 commit fc51ddf

30 files changed

+5879
-2
lines changed

.env.example

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
AWS_REGION=eu-west-3
2+
#For direct invocaiton
3+
LAMBDA_FUNCTION_NAME=example_lambda
4+
# For SQS trigger
5+
SQS_QUEUE_NAME=example_lambda_queue

.gitignore

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2+
3+
# dependencies
4+
/node_modules
5+
/.pnp
6+
.pnp.*
7+
.yarn/*
8+
!.yarn/patches
9+
!.yarn/plugins
10+
!.yarn/releases
11+
!.yarn/versions
12+
13+
# testing
14+
/coverage
15+
16+
# next.js
17+
/.next/
18+
/out/
19+
20+
# production
21+
/build
22+
23+
# misc
24+
.DS_Store
25+
*.pem
26+
27+
# debug
28+
npm-debug.log*
29+
yarn-debug.log*
30+
yarn-error.log*
31+
.pnpm-debug.log*
32+
33+
# env files (can opt-in for committing if needed)
34+
.env
35+
36+
# vercel
37+
.vercel
38+
39+
# typescript
40+
*.tsbuildinfo
41+
next-env.d.ts
42+
43+
# Lambda build artifacts
44+
/backend/lambda/*.zip
45+
46+
# Terraform
47+
backend/terraform/.terraform/
48+
backend/terraform/terraform.tfstate
49+
backend/terraform/terraform.tfstate.backup
50+
backend/terraform/.terraform.lock.hcl

README.md

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,34 @@
1-
# nextjs-lambda
2-
Sample NextJS / Lambda application
1+
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
2+
3+
terraform init
4+
terraform apply
5+
6+
## Getting Started
7+
8+
# Step 1: build the lambda function using the provided Bash script, or Powershell script. It is not exactly a build, it only creates a zip file containing the Python file.
9+
10+
```bash
11+
cd backend/lambda
12+
./build.sh
13+
```
14+
15+
# Step 2: bootstrap the infrastructure in AWS
16+
17+
```bash
18+
cd backend/terraform
19+
terraform apply
20+
```
21+
22+
This is just a quick example, the state is stored locally, and there are variables.
23+
24+
# Step 3: Configure the application's .env file
25+
26+
You can simply copy the provided .env.example file and save it as .env
27+
28+
# Step 4: Run the development server
29+
30+
```bash
31+
pnpm dev
32+
```
33+
34+
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.

app/api/example/route.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { NextRequest, NextResponse } from 'next/server';
2+
import { LambdaClient, InvokeCommand } from '@aws-sdk/client-lambda';
3+
4+
export async function POST(request: NextRequest) {
5+
try {
6+
const { name } = await request.json();
7+
8+
if (!name || typeof name !== 'string') {
9+
return NextResponse.json(
10+
{ error: 'Name is required and must be a string' },
11+
{ status: 400 }
12+
);
13+
}
14+
// Call Lambda using AWS SDK
15+
16+
const lambdaClient = new LambdaClient({ region: process.env.AWS_REGION || 'us-east-1' });
17+
18+
const lambdaPayload = {
19+
Records: [
20+
{
21+
body: JSON.stringify({ input_name: name })
22+
}
23+
]
24+
};
25+
26+
const command = new InvokeCommand({
27+
FunctionName: process.env.LAMBDA_FUNCTION_NAME || 'your-lambda-function-name',
28+
Payload: JSON.stringify(lambdaPayload),
29+
});
30+
31+
const lambdaResponse = await lambdaClient.send(command);
32+
const lambdaResult = JSON.parse(new TextDecoder().decode(lambdaResponse.Payload));
33+
34+
console.log('Lambda result:', lambdaResult);
35+
return NextResponse.json(lambdaResult);
36+
} catch (error) {
37+
return NextResponse.json(
38+
{ error: 'Invalid request' },
39+
{ status: 400 }
40+
);
41+
}
42+
}

app/favicon.ico

25.3 KB
Binary file not shown.

app/globals.css

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
@import "tailwindcss";
2+
@import "tw-animate-css";
3+
4+
@custom-variant dark (&:is(.dark *));
5+
6+
@theme inline {
7+
--color-background: var(--background);
8+
--color-foreground: var(--foreground);
9+
--font-sans: var(--font-geist-sans);
10+
--font-mono: var(--font-geist-mono);
11+
--color-sidebar-ring: var(--sidebar-ring);
12+
--color-sidebar-border: var(--sidebar-border);
13+
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
14+
--color-sidebar-accent: var(--sidebar-accent);
15+
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
16+
--color-sidebar-primary: var(--sidebar-primary);
17+
--color-sidebar-foreground: var(--sidebar-foreground);
18+
--color-sidebar: var(--sidebar);
19+
--color-chart-5: var(--chart-5);
20+
--color-chart-4: var(--chart-4);
21+
--color-chart-3: var(--chart-3);
22+
--color-chart-2: var(--chart-2);
23+
--color-chart-1: var(--chart-1);
24+
--color-ring: var(--ring);
25+
--color-input: var(--input);
26+
--color-border: var(--border);
27+
--color-destructive: var(--destructive);
28+
--color-accent-foreground: var(--accent-foreground);
29+
--color-accent: var(--accent);
30+
--color-muted-foreground: var(--muted-foreground);
31+
--color-muted: var(--muted);
32+
--color-secondary-foreground: var(--secondary-foreground);
33+
--color-secondary: var(--secondary);
34+
--color-primary-foreground: var(--primary-foreground);
35+
--color-primary: var(--primary);
36+
--color-popover-foreground: var(--popover-foreground);
37+
--color-popover: var(--popover);
38+
--color-card-foreground: var(--card-foreground);
39+
--color-card: var(--card);
40+
--radius-sm: calc(var(--radius) - 4px);
41+
--radius-md: calc(var(--radius) - 2px);
42+
--radius-lg: var(--radius);
43+
--radius-xl: calc(var(--radius) + 4px);
44+
}
45+
46+
:root {
47+
--radius: 0.625rem;
48+
--background: oklch(1 0 0);
49+
--foreground: oklch(0.145 0 0);
50+
--card: oklch(1 0 0);
51+
--card-foreground: oklch(0.145 0 0);
52+
--popover: oklch(1 0 0);
53+
--popover-foreground: oklch(0.145 0 0);
54+
--primary: oklch(0.205 0 0);
55+
--primary-foreground: oklch(0.985 0 0);
56+
--secondary: oklch(0.97 0 0);
57+
--secondary-foreground: oklch(0.205 0 0);
58+
--muted: oklch(0.97 0 0);
59+
--muted-foreground: oklch(0.556 0 0);
60+
--accent: oklch(0.97 0 0);
61+
--accent-foreground: oklch(0.205 0 0);
62+
--destructive: oklch(0.577 0.245 27.325);
63+
--border: oklch(0.922 0 0);
64+
--input: oklch(0.922 0 0);
65+
--ring: oklch(0.708 0 0);
66+
--chart-1: oklch(0.646 0.222 41.116);
67+
--chart-2: oklch(0.6 0.118 184.704);
68+
--chart-3: oklch(0.398 0.07 227.392);
69+
--chart-4: oklch(0.828 0.189 84.429);
70+
--chart-5: oklch(0.769 0.188 70.08);
71+
--sidebar: oklch(0.985 0 0);
72+
--sidebar-foreground: oklch(0.145 0 0);
73+
--sidebar-primary: oklch(0.205 0 0);
74+
--sidebar-primary-foreground: oklch(0.985 0 0);
75+
--sidebar-accent: oklch(0.97 0 0);
76+
--sidebar-accent-foreground: oklch(0.205 0 0);
77+
--sidebar-border: oklch(0.922 0 0);
78+
--sidebar-ring: oklch(0.708 0 0);
79+
}
80+
81+
.dark {
82+
--background: oklch(0.145 0 0);
83+
--foreground: oklch(0.985 0 0);
84+
--card: oklch(0.205 0 0);
85+
--card-foreground: oklch(0.985 0 0);
86+
--popover: oklch(0.205 0 0);
87+
--popover-foreground: oklch(0.985 0 0);
88+
--primary: oklch(0.922 0 0);
89+
--primary-foreground: oklch(0.205 0 0);
90+
--secondary: oklch(0.269 0 0);
91+
--secondary-foreground: oklch(0.985 0 0);
92+
--muted: oklch(0.269 0 0);
93+
--muted-foreground: oklch(0.708 0 0);
94+
--accent: oklch(0.269 0 0);
95+
--accent-foreground: oklch(0.985 0 0);
96+
--destructive: oklch(0.704 0.191 22.216);
97+
--border: oklch(1 0 0 / 10%);
98+
--input: oklch(1 0 0 / 15%);
99+
--ring: oklch(0.556 0 0);
100+
--chart-1: oklch(0.488 0.243 264.376);
101+
--chart-2: oklch(0.696 0.17 162.48);
102+
--chart-3: oklch(0.769 0.188 70.08);
103+
--chart-4: oklch(0.627 0.265 303.9);
104+
--chart-5: oklch(0.645 0.246 16.439);
105+
--sidebar: oklch(0.205 0 0);
106+
--sidebar-foreground: oklch(0.985 0 0);
107+
--sidebar-primary: oklch(0.488 0.243 264.376);
108+
--sidebar-primary-foreground: oklch(0.985 0 0);
109+
--sidebar-accent: oklch(0.269 0 0);
110+
--sidebar-accent-foreground: oklch(0.985 0 0);
111+
--sidebar-border: oklch(1 0 0 / 10%);
112+
--sidebar-ring: oklch(0.556 0 0);
113+
}
114+
115+
@layer base {
116+
* {
117+
@apply border-border outline-ring/50;
118+
}
119+
body {
120+
@apply bg-background text-foreground;
121+
}
122+
}

app/layout.tsx

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import type { Metadata } from "next";
2+
import { Geist, Geist_Mono } from "next/font/google";
3+
import "./globals.css";
4+
5+
const geistSans = Geist({
6+
variable: "--font-geist-sans",
7+
subsets: ["latin"],
8+
});
9+
10+
const geistMono = Geist_Mono({
11+
variable: "--font-geist-mono",
12+
subsets: ["latin"],
13+
});
14+
15+
export const metadata: Metadata = {
16+
title: "Create Next App",
17+
description: "Generated by create next app",
18+
};
19+
20+
export default function RootLayout({
21+
children,
22+
}: Readonly<{
23+
children: React.ReactNode;
24+
}>) {
25+
return (
26+
<html lang="en">
27+
<body
28+
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
29+
>
30+
{children}
31+
</body>
32+
</html>
33+
);
34+
}

app/page.tsx

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
"use client";
2+
3+
import { Button } from "@/components/ui/button";
4+
import { Input } from "@/components/ui/input";
5+
import { useState } from "react";
6+
7+
export default function Home() {
8+
9+
const [input, setInput] = useState("");
10+
11+
interface ApiResponse {
12+
statusCode: number;
13+
body: {
14+
original: string;
15+
reversed: string;
16+
uppercase: string;
17+
lowercase: string;
18+
length: number;
19+
vowels: number;
20+
consonants: number;
21+
initials: string;
22+
is_palindrome: boolean;
23+
greeting: string;
24+
};
25+
}
26+
27+
const [result, setResult] = useState<ApiResponse | null>(null);
28+
29+
const handleOnClick = async () => {
30+
const response = await fetch('/api/example', {
31+
method: 'POST',
32+
headers: {
33+
'Content-Type': 'application/json',
34+
},
35+
body: JSON.stringify({ name: input }),
36+
});
37+
setResult(await response.json());
38+
}
39+
// {process.env.LAMBDA_FUNCTION_NAME}
40+
return (
41+
<div className="flex min-h-screen items-center justify-center bg-zinc-50 font-sans dark:bg-black">
42+
<main className="flex min-h-screen w-full max-w-3xl flex-col items-center justify-between py-32 px-16 bg-white dark:bg-black sm:items-start">
43+
44+
<div className="flex flex-col items-center gap-6 text-center sm:items-start sm:text-left">
45+
<h1 className="text-3xl font-semibold leading-10 tracking-tight text-black dark:text-zinc-50">
46+
AWS Lambda Demo with Next.js</h1>
47+
<p>Enter some text in the input below, then click <b>Submit</b> </p>
48+
49+
<div className="w-full text-left space-y-2">
50+
<p className="font-medium">What happens under the hood:</p>
51+
<ul style={{ listStyleType: 'disc', paddingLeft: '1.5rem' }} className="space-y-2">
52+
<li style={{ display: 'list-item' }}>The Next.js API route <code className="bg-zinc-100 dark:bg-zinc-800 px-1 rounded">/api/example</code> receives the input.</li>
53+
<li style={{ display: 'list-item' }}>It invokes an AWS Lambda function using the AWS SDK.</li>
54+
<li style={{ display: 'list-item' }}>The Lambda function processes the input (reverses it, counts vowels, etc.) and returns the result.</li>
55+
<li style={{ display: 'list-item' }}>The API route sends the Lambda response back to the frontend, which displays it below.</li>
56+
</ul>
57+
</div>
58+
59+
<div className="flex flex-row gap-2">
60+
<Input onChange={(e) => setInput(e.target.value)} value={input} placeholder="Enter your name" />
61+
<Button className="ml-2" onClick={handleOnClick}>Submit</Button>
62+
</div>
63+
64+
{result && result.statusCode === 200 && (
65+
<div className="mt-4">
66+
<p>Original: {result.body.original}</p>
67+
<p>Reversed: {result.body.reversed}</p>
68+
<p>Uppercase: {result.body.uppercase}</p>
69+
<p>Lowercase: {result.body.lowercase}</p>
70+
<p>Length: {result.body.length}</p>
71+
<p>Vowels: {result.body.vowels}</p>
72+
<p>Consonants: {result.body.consonants}</p>
73+
<p>Initials: {result.body.initials}</p>
74+
<p>Is Palindrome: {result.body.is_palindrome ? 'Yes' : 'No'}</p>
75+
<p>{result.body.greeting}</p>
76+
</div>
77+
)}
78+
</div>
79+
</main>
80+
</div>
81+
);
82+
}

0 commit comments

Comments
 (0)