Introduction#

This GitHub shows how to create a simple nextjs app with backend serverless using lambda and api gateway

  • Create APIs for generating signed url s3
  • Upload and download using the singed url

Lambda Functions#

We need to create a lambda function for generating signed url for downloading file from s3

import { Context, APIGatewayProxyResult, APIGatewayEvent } from "aws-lambda";
import { S3Client, GetObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
const s3Client = new S3Client({
region: process.env.REGION,
});
export const handler = async (
event: APIGatewayEvent,
context: Context
): Promise<APIGatewayProxyResult> => {
console.log(`Event: ${JSON.stringify(event, null, 2)}`);
console.log(`Context: ${JSON.stringify(context, null, 2)}`);
// parse argument from context
let key: string = "";
let url: string = "";
try {
key = (event.queryStringParameters as any).key as string;
} catch (error) {}
const command = new GetObjectCommand({
Bucket: process.env.BUCKET,
Key: key,
});
try {
url = await getSignedUrl(s3Client as any, command as any, {
expiresIn: 3600,
});
} catch (error) {}
return {
statusCode: 200,
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers": "Content-Type",
"Access-Control-Allow-Methods": "OPTIONS,GET",
},
body: JSON.stringify({
signedUrl: url,
}),
};
};

Then similarly, create a lambda function for generating signed url for uplading images to s3

const command = new PutObjectCommand({
Bucket: process.env.BUCKET,
Key: key,
});
try {
url = await getSignedUrl(s3Client as any, command as any, {
expiresIn: 3600,
});
} catch (error) {}

API Stack#

Create an API Gateway and integrate with two lambda functions. First, create a role for lambda functions

// role for lambda
const role = new cdk.aws_iam.Role(this, "RoleForLamdbaNext", {
assumedBy: new cdk.aws_iam.ServicePrincipal("lambda.amazonaws.com"),
});
role.addManagedPolicy(
cdk.aws_iam.ManagedPolicy.fromAwsManagedPolicyName(
"service-role/AWSLambdaBasicExecutionRole"
)
);
role.addToPolicy(
new cdk.aws_iam.PolicyStatement({
effect: Effect.ALLOW,
resources: ["*"],
actions: ["s3:*"],
})
);

Create the first lambda function in Typscript, which is for generating download signed url

const downloadSignedUrl = new NodejsFunction(this, "GetSignedUrlDownloadFunc", {
entry: path.join(__dirname, "./../lambda/handler_download.ts"),
handler: "handler",
bundling: {
externalModules: [],
},
environment: {
REGION: props.region,
BUCKET: props.bucket,
},
role: role,
});

Create second lambda function, which is for generating upload signed url

const uploadSignedUrl = new NodejsFunction(this, "GetSignedUrlUploadFunc", {
entry: path.join(__dirname, "./../lambda/handler_upload.ts"),
handler: "handler",
bundling: {
externalModules: [],
},
environment: {
REGION: props.region,
BUCKET: props.bucket,
},
role: role,
});

Integrate with API Gateway

const apiGw = new cdk.aws_apigateway.RestApi(this, "ApiGatewayNextApp", {
restApiName: "ApiGatewayNextApp",
});
// api gateawy resources
const download = apiGw.root.addResource("download");
// integrate lambda with api gateway
download.addMethod(
"GET",
new cdk.aws_apigateway.LambdaIntegration(downloadSignedUrl)
);
// api gateway resources
const upload = apiGw.root.addResource("upload");
upload.addMethod(
"GET",
new cdk.aws_apigateway.LambdaIntegration(uploadSignedUrl)
);

Signed URL S3#

  • Download image from signed url
  • Upload image from signed url

Let download an image from a signed url

const response = await axios.get(config.API_DOWNLOAD_URL, {
params: {
key: "pirf/tree.jpg",
},
});
console.log(response.data.signedUrl);

Then let upload image from a singed url using axios

const submitTest = async () => {
const fileName = file ? file.name : "";
const response = await axios.get(config.API_UPLOAD_URL, {
params: {
key: "pirf/" + fileName,
},
});
await axios.put(response.data.signedUrl as string, file);
const signedDownloadUrl = await axios.get(config.API_DOWNLOAD_URL, {
params: {
key: "pirf/" + fileName,
},
});
setUrl(signedDownloadUrl.data.signedUrl);
return false;
};

Finally let upload an image from a signed url using fetch

const submit = async () => {
const fileName = file ? file.name : "";
const response = await axios.get(config.API_UPLOAD_URL, {
params: {
key: "pirf/" + fileName,
},
});
const upload = await fetch(response.data.signedUrl as string, {
method: "PUT",
body: file,
});
const signedDownloadUrl = await axios.get(config.API_DOWNLOAD_URL, {
params: {
key: "pirf/" + fileName,
},
});
setUrl(signedDownloadUrl.data.signedUrl);
return false;
};

S3 CORS#

[
{
"AllowedHeaders": ["*"],
"AllowedMethods": ["GET", "PUT", "POST"],
"AllowedOrigins": ["*"],
"ExposeHeaders": []
}
]

Reference#