• בלוג
  • מדריך: איך להעלות קבצים ל AWS S3 מתוך יישום Node.JS

מדריך: איך להעלות קבצים ל AWS S3 מתוך יישום Node.JS

בקורס Node.JS פה באתר אני מלמד איך לשמור קבצים בתיקיה מקומית על השרת או בבסיס נתונים של Mongo. בעקבות שאלה של תלמיד חשבתי להוסיף כאן מדריך איך להעלות את הקבצים גם ל AWS S3 למי שמעדיף.

1. מה אנחנו בונים

בדוגמה במדריך זה אני אבנה מערכת וובית פשוטה להצגת תמונות. המערכת מורכבת מ:

  1. סקריפט בשם upload שמעלה קובץ לדלי ב S3.
  2. אתר ריאקט שמציג את כל התמונות ששמורות בדלי.

2. קטנה על הרשאות

מנגנון ההרשאות של AWS הוא מאוד מורכב והוא כנראה הסיבה המרכזית בגללה אנשים מסתבכים עם AWS. בדוגמה שלנו אני יוצר Bucket שמחזיק תמונות. כל התמונות ציבוריות וכל אחד יכול לראות את כל התמונות של כולם. זה הופך את החיים להרבה יותר פשוטים מבחינת הקידוד אבל כמובן שגישה זו לא מתאימה לכל מערכת.

אם רוצים לייצר קבצים שיהיה נגישים רק לאנשים מסוימים ולא נגישים לאחרים צריך לשמור את כל הקבצים פרטיים וכל פעם שמשתמש רוצה לגשת לאחד מהם לייצר עבורו לינק מיוחד וספציפי לתמונה. אמזון תומכים בזה עם דבר שנקרא Presigned URL אבל הוא מחוץ לסקופ של מדריך זה.

3. איך יוצרים Bucket

אני מניח שיש לכם חשבון AWS ושיצרתם אסימון גישה, ושיש לכם ביד את ה id וה secret של אסימון הגישה. לפרטים איך ליצור אסימון גישה ולקבל את פרטי הגישה אפשר להסתכל במדריך הזה: https://docs.aws.amazon.com/powershell/latest/userguide/pstools-appendix-sign-up.html

בעבודה עם Node.JS יש לנו חבילה נחמדה בשם aws-sdk שעוזרת ל Node להתחבר ל AWS. אני מתקין את החבילה עם:

$ npm install --save aws-sdk

ויוצר קובץ חדש בפרויקט - אצלי הוא נקרא src/lib/mys3.js עם התוכן הבא:

const AWS = require('aws-sdk');

// Enter copied or downloaded access ID and secret key here
const ID = process.env.AWS_ID;
const SECRET = process.env.AWS_SECRET;

const s3 = new AWS.S3({
    accessKeyId: ID,
    secretAccessKey: SECRET
});

module.exports = s3;

בשביל לא לשמור בקוד את מפתחות הגישה שמרתי אותם בתור משתני סביבה. הקוד עצמו פשוט יוצר אוביקט AWS.S3 חדש ומייצא אותו, כדי שלקבצים אחרים יהיה יותר נוח לעבוד עם s3.

באותה תיקיה אני יוצר עוד קובץ בשם src/lib/globals.js עם התוכן הבא:

// The name of the bucket that you have created
exports.BUCKET_NAME = 'ynonp-s3-photos';

שמגדיר את שם הדלי בו אני אשמור את התמונות.

עכשיו אנחנו מוכנים לכתוב את הסקריפט הראשון שיוצר את הדלי. אני יוצר תיקיה בשם utils ובתוכה הקובץ utils/create-bucket.js עם התוכן הבא:

const AWS = require('aws-sdk');
const { BUCKET_NAME } = require('../src/lib/globals');
const s3 = require('../src/lib/mys3');

const params = {
    Bucket: BUCKET_NAME,
    CreateBucketConfiguration: {
        // Set your region here
        LocationConstraint: "eu-west-1"
    }
};

s3.createBucket(params, function(err, data) {
    if (err) console.log(err, err.stack);
    else console.log('Bucket Created Successfully', data.Location);
});

את הקובץ אפשר להריץ משורת הפקודה:

$ node utils/create-bucket.js

ומספיק להריץ אותו פעם אחת כדי ליצור את הדלי. שימו לב רק לבחור שם ייחודי לדלי (באמצעות שינוי בקובץ globals.js) לפני שמנסים ליצור. בכל מקרה אם השם שלכם לא מספיק מיוחד אז s3 יצעק עליכם ולא ייצור את הדלי.

4. איך מעלים קובץ

אחרי שיש לנו דלי נוכל להמשיך לסקריפט השני, גם הוא נועד להרצה משורת הפקודה ולפני השימוש בשרת. ניצור קובץ בשם utils/upload.js עם התוכן הבא:

const fs = require('fs');
const { BUCKET_NAME } = require('../src/lib/globals');
const s3 = require('../src/lib/mys3');

const filename = process.argv[2];
console.log(filename);

const uploadFile = (fileName) => {
    // Read content from the file
    const fileContent = fs.readFileSync(fileName);

    // Setting up S3 upload parameters
    const params = {
      Bucket: BUCKET_NAME,
      Key: 'cat.jpg', // File name you want to save as in S3
      Body: fileContent,
      ACL:'public-read'
    };

    // Uploading files to the bucket
    s3.upload(params, function(err, data) {
        if (err) {
            throw err;
        }
        console.log(`File uploaded successfully. ${data.Location}`);
    });
};

uploadFile(filename);

הסקריפט נועד להרצה משורת הפקודה בצירוף שם קובץ והוא מעלה את הקובץ לדלי שיצרנו. הרצה לדוגמה - בהנחה שיש לכם קובץ בשם cat.jpg בתיקיה תיראה כך:

$ node utils/uploads.js cat.jpg

5. איך מקבלים רשימה של כל הקבצים ב Bucket

והחלק האחרון במערכת הוא פניה ל S3 כדי לקבל רשימה של כל הקבצים בדלי. ניצור קובץ בשם src/lib/images.js עם התוכן הבא:

const s3 = require('./mys3');
const { BUCKET_NAME } = require('./globals');

var params = {
  Bucket: BUCKET_NAME,
};


export async function listImages() {
  console.log('listing images');
  const data = await s3.listObjectsV2(params).promise();
  const images = data.Contents.map(item => `https://${BUCKET_NAME}.s3-eu-west-1.amazonaws.com/${item.Key}`);
  return images;
}

הפונקציה listImages פונה ל s3, מעבירה את שם הדלי ומקבלת את רשימת כל הקבצים בדלי. הרשימה מגיעה בפורמט כזה:

{
  IsTruncated: false,
  Contents: [
    {
      Key: 'cat.jpg',
      LastModified: 2022-01-19T13:38:18.000Z,
      ETag: '"df71c370626f34e60ae6c1df9b64eb09"',
      Size: 138421,
      StorageClass: 'STANDARD'
    }
  ],
  Name: 'ynonp-s3-photos',
  Prefix: '',
  MaxKeys: 1000,
  CommonPrefixes: [],
  KeyCount: 1
}

אני משתמש ב key של כל קובץ ומצרף אליו את שם הדלי כדי לקבל URL לתמונה. צריך להגיד - הסיפור הזה עובד כל כך פשוט רק בגלל שכל התמונות שלי הן ציבוריות. אם התמונות היו פרטיות הייתי צריך לפנות לפונקציה נוספת כדי לקבל Presigned URL לכל תמונה.

האפליקציה שכתבתי היא ב Next.JS ולכן אני יכול לערבב קוד צד שרת וקוד צד לקוח באותו קובץ. במקרה שלי הקובץ נקרא src/pages/index.js וזה התוכן שלו:

import Head from 'next/head'
import styles from '../../styles/Home.module.css'
import { listImages } from '../lib/images';


export default function Home(props) {
  const { images } = props;
  return (
    <div className={styles.container}>
      <Head>
        <title>Create Next App</title>
        <meta name="description" content="Generated by create next app" />
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <main className={styles.main}>
        <p>Hello World</p>
        {images.map((imgSource) => (
          <img src={imgSource} />
        ))}
      </main>
    </div>
  )
}

export async function getServerSideProps() {
  const images = await listImages();
  return {
    props: { images },
  };
}

6. איך מפעילים אצלכם

בשביל להפעיל את המערכת אצלכם תצטרכו לעבור מספר שלבים:

  1. שכפלו את המאגר המלא שלי מכאן: https://github.com/ynonp/s3-photos-demo

  2. צרו משתמש ב S3 וקבלו את פרטי הגישה

  3. צרו משתני סביבה בשם AWS_ID ו AWS_SECRET עם פרטי הגישה שלכם.

  4. בחרו שם ל Bucket ורשמו אותו בקובץ src/lib/globals.js במקום השם שאני בחרתי.

  5. הפעילו משורת הפקודה את הפקודה שיוצרת את ה Bucket וודאו שהכל עובד.

  6. הפעילו משורת הפקודה כמה פעמים את הסקריפט upload עם כמה תמונות כדי להעלות אותן ל Bucket

  7. הפעילו את המערכת עם npm run dev וכנסו למסך הראשי ב localhost:3000.

אם הכל הלך כמו שצריך תוכלו לראות על המסך את כל התמונות שהעליתם.