-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Implemented S3 upload * Updated README * Cleaned up dependencies
- Loading branch information
1 parent
52985dd
commit df2359c
Showing
16 changed files
with
279 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
STORAGE_ACCESS_KEY= | ||
STORAGE_SECRET= | ||
STORAGE_REGION= | ||
STORAGE_BUCKET= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
module.exports = { | ||
extends: ["@remix-run/eslint-config", "@remix-run/eslint-config/node"], | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
node_modules | ||
|
||
/.cache | ||
/build | ||
/public/build | ||
.env |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
# Upload images to S3 | ||
|
||
This is a simple example of using the remix built-in [uploadHandler](https://remix.run/docs/en/v1/api/remix#uploadhandler) and Form with multipart data to upload a file with the built-in local uploader and upload an image file to S3 with a custom uploader and display it. You can test it locally by running the dev server and opening the path `/s3-upload` in your browser. | ||
|
||
The relevent files are: | ||
|
||
``` | ||
├── app | ||
│ ├── routes | ||
│ │ ├── s3-upload.tsx // upload to S3 | ||
│ └── utils | ||
│ └── s3.server.ts // init S3 client on server side | ||
|── .env // holds AWS S3 credentails | ||
``` | ||
|
||
## Steps to set up an S3 bucket | ||
|
||
- Sign up for an [AWS account](https://portal.aws.amazon.com/billing/signup) - this will require a credit card | ||
- Create an S3 bucket in your desired region | ||
- Create an access key pair for an IAM user that has access to the bucket | ||
- Copy the .env.sample to .env and fill in the S3 bucket, the region as well as the access key and secret key from the IAM user | ||
|
||
Note: in order for the image to be displayed after being uploaded to your S3 bucket in this example, the bucket needs to have public access enabled, which is potentially dangerous. | ||
|
||
> :warning: Lambda imposes a [limit of 6MB](https://docs.aws.amazon.com/lambda/latest/dg/gettingstarted-limits.html) on the invocation payload size. If you use this example with Remix running on Lambda, you can only update files with a size smaller than 6MB. | ||
Open this example on [CodeSandbox](https://codesandbox.com): | ||
|
||
[![Open in CodeSandbox](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/github/remix-run/remix/tree/main/examples/file-and-s3-upload) | ||
|
||
## Related Links | ||
|
||
- [Handle Multiple Part Forms (File Uploads)](https://remix.run/docs/en/v1/api/remix#unstable_parsemultipartformdata-node) | ||
- [Upload Handler](https://remix.run/docs/en/v1/api/remix#uploadhandler) | ||
- [Custom Uploader](https://remix.run/docs/en/v1/api/remix#custom-uploadhandler) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
import { RemixBrowser } from "@remix-run/react"; | ||
import { hydrate } from "react-dom"; | ||
|
||
hydrate(<RemixBrowser />, document); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import type { EntryContext } from "@remix-run/node"; | ||
import { RemixServer } from "@remix-run/react"; | ||
import { renderToString } from "react-dom/server"; | ||
|
||
export default function handleRequest( | ||
request: Request, | ||
responseStatusCode: number, | ||
responseHeaders: Headers, | ||
remixContext: EntryContext | ||
) { | ||
const markup = renderToString( | ||
<RemixServer context={remixContext} url={request.url} /> | ||
); | ||
|
||
responseHeaders.set("Content-Type", "text/html"); | ||
|
||
return new Response("<!DOCTYPE html>" + markup, { | ||
status: responseStatusCode, | ||
headers: responseHeaders, | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import type { MetaFunction } from "@remix-run/node"; | ||
import { | ||
Links, | ||
LiveReload, | ||
Meta, | ||
Outlet, | ||
Scripts, | ||
ScrollRestoration, | ||
} from "@remix-run/react"; | ||
|
||
export const meta: MetaFunction = () => ({ | ||
charset: "utf-8", | ||
title: "New Remix App", | ||
viewport: "width=device-width,initial-scale=1", | ||
}); | ||
|
||
export default function App() { | ||
return ( | ||
<html lang="en"> | ||
<head> | ||
<Meta /> | ||
<Links /> | ||
</head> | ||
<body> | ||
<Outlet /> | ||
<ScrollRestoration /> | ||
<Scripts /> | ||
<LiveReload /> | ||
</body> | ||
</html> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import type { ActionFunction, UploadHandler } from "@remix-run/node"; | ||
import { | ||
json, | ||
unstable_composeUploadHandlers as composeUploadHandlers, | ||
unstable_createMemoryUploadHandler as createMemoryUploadHandler, | ||
unstable_parseMultipartFormData as parseMultipartFormData, | ||
} from "@remix-run/node"; | ||
import { Form, useActionData } from "@remix-run/react"; | ||
import { s3UploadHandler } from "~/utils/s3.server"; | ||
|
||
type ActionData = { | ||
errorMsg?: string; | ||
imgSrc?: string; | ||
imgDesc?: string; | ||
}; | ||
|
||
export const action: ActionFunction = async ({ request }) => { | ||
const uploadHandler: UploadHandler = composeUploadHandlers( | ||
s3UploadHandler, | ||
createMemoryUploadHandler() | ||
); | ||
const formData = await parseMultipartFormData(request, uploadHandler); | ||
const imgSrc = formData.get("img"); | ||
const imgDesc = formData.get("desc"); | ||
console.log(imgDesc) | ||
if (!imgSrc) { | ||
return json({ | ||
error: "Something went wrong while uploading", | ||
}); | ||
} | ||
return json({ | ||
imgSrc, | ||
imgDesc, | ||
}); | ||
}; | ||
|
||
export default function Index() { | ||
const data = useActionData<ActionData>(); | ||
return ( | ||
<> | ||
<Form method="post" encType="multipart/form-data"> | ||
<label htmlFor="img-field">Image to upload</label> | ||
<input id="img-field" type="file" name="img" accept="image/*" /> | ||
<label htmlFor="img-desc">Image description</label> | ||
<input id="img-desc" type="text" name="desc" /> | ||
<button type="submit">Upload to S3</button> | ||
</Form> | ||
{data?.errorMsg && <h2>{data.errorMsg}</h2>} | ||
{data?.imgSrc && ( | ||
<> | ||
<div>File has been uploaded to S3 and is available under the following URL (if the bucket has public access enabled):</div> | ||
<div>{data.imgSrc}</div> | ||
<img src={data.imgSrc} alt={data.imgDesc || "Uploaded image from S3"} /> | ||
|
||
</> | ||
)} | ||
</> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import AWS from "aws-sdk" | ||
import type { UploadHandler } from "@remix-run/node" | ||
import { writeAsyncIterableToWritable } from "@remix-run/node" | ||
import { PassThrough } from "stream" | ||
|
||
const { STORAGE_ACCESS_KEY, STORAGE_SECRET, STORAGE_REGION, STORAGE_BUCKET } = process.env | ||
|
||
if (!(STORAGE_ACCESS_KEY && STORAGE_SECRET && STORAGE_REGION && STORAGE_BUCKET)) { | ||
throw new Error(`Storage is missing required configuration.`) | ||
} | ||
|
||
const uploadStream = ({ Key }: Pick<AWS.S3.Types.PutObjectRequest, 'Key'>) => { | ||
const s3 = new AWS.S3({ | ||
credentials: { | ||
accessKeyId: STORAGE_ACCESS_KEY, | ||
secretAccessKey: STORAGE_SECRET, | ||
}, | ||
region: STORAGE_REGION, | ||
}) | ||
const pass = new PassThrough() | ||
return { | ||
writeStream: pass, | ||
promise: s3.upload({ Bucket: STORAGE_BUCKET, Key, Body: pass }).promise(), | ||
} | ||
} | ||
|
||
export async function uploadStreamToS3(data: any, filename: string) { | ||
const stream = uploadStream({ | ||
Key: filename, | ||
}) | ||
await writeAsyncIterableToWritable(data, stream.writeStream) | ||
const file = await stream.promise | ||
return file.Location | ||
} | ||
|
||
export const s3UploadHandler: UploadHandler = async ({ | ||
name, | ||
filename, | ||
data, | ||
}) => { | ||
if (name !== "img") { | ||
return undefined; | ||
} | ||
const uploadedFileLocation = await uploadStreamToS3(data, filename!) | ||
return uploadedFileLocation | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
{ | ||
"private": true, | ||
"sideEffects": false, | ||
"scripts": { | ||
"build": "remix build", | ||
"dev": "remix dev", | ||
"start": "remix-serve build" | ||
}, | ||
"dependencies": { | ||
"@remix-run/node": "1.5.1", | ||
"@remix-run/react": "1.5.1", | ||
"@remix-run/serve": "1.5.1", | ||
"aws-sdk": "^2.1152.0", | ||
"cloudinary": "^1.28.1", | ||
"react": "^17.0.2", | ||
"react-dom": "^17.0.2" | ||
}, | ||
"devDependencies": { | ||
"@remix-run/dev": "1.5.1", | ||
"@remix-run/eslint-config": "1.5.1", | ||
"@types/react": "^17.0.39", | ||
"@types/react-dom": "^17.0.13", | ||
"eslint": "^8.10.0", | ||
"typescript": "^4.6.2" | ||
}, | ||
"engines": { | ||
"node": ">=14" | ||
} | ||
} |
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
/** | ||
* @type {import('@remix-run/dev').AppConfig} | ||
*/ | ||
module.exports = { | ||
ignoredRouteFiles: ["**/.*"], | ||
// appDirectory: "app", | ||
// assetsBuildDirectory: "public/build", | ||
// serverBuildPath: "build/index.js", | ||
// publicPath: "/build/", | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
/// <reference types="@remix-run/dev" /> | ||
/// <reference types="@remix-run/node/globals" /> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
{ | ||
"hardReloadOnChange": true, | ||
"container": { | ||
"port": 3000 | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
{ | ||
"include": ["remix.env.d.ts", "**/*.ts", "**/*.tsx"], | ||
"compilerOptions": { | ||
"lib": ["DOM", "DOM.Iterable", "ES2019"], | ||
"isolatedModules": true, | ||
"esModuleInterop": true, | ||
"jsx": "react-jsx", | ||
"moduleResolution": "node", | ||
"resolveJsonModule": true, | ||
"target": "ES2019", | ||
"strict": true, | ||
"allowJs": true, | ||
"forceConsistentCasingInFileNames": true, | ||
"baseUrl": ".", | ||
"paths": { | ||
"~/*": ["./app/*"] | ||
}, | ||
|
||
// Remix takes care of building everything in `remix build`. | ||
"noEmit": true | ||
} | ||
} |