DEV Community

Karan
Karan

Posted on

How to create service like Amazon S3 image upload for free using GitHub.

Hello folks! Welcome to this blog on developing the service like s3 image upload for freely using GitHub.

I've been working with technology for over two years now, And I get to know that we have been using s3 image upload service for some prize but if any beginner who's just creating his application in nest js or node js he/she can use this trick to upload the images to GitHub and by getting the URL of that image So that he/she can use it in the front-end So, this weekend, I decided to create this functionality, specifically NestJS.

Uploading images to the web can be a hassle, especially if you're working on a personal project or a small website without a budget for paid services like Amazon S3. Luckily, GitHub provides a neat solution to host your images for free!

GitHub is primarily a platform for hosting source code repositories, but it can also serve as a simple and free image hosting service. Here's how you can leverage GitHub for your image uploading needs:
1.Create a New Repository :
First, you'll need a GitHub repository to store your images. Go to github.com and create a new repository. You can name it something like "my-images" or "image-host". Make sure to initialize the repository with a README file. and keep it as public as your need!

I'll consider you have created basic setup for NestJS.

you can install required package:

npm install @octokit/rest
Enter fullscreen mode Exit fullscreen mode

Simply you can follow my code for creating the github.helper.ts

import { Octokit } from '@octokit/rest';
import { encode } from 'base-64';
import { ConfigService } from '@nestjs/config';
import { Injectable, InternalServerErrorException } from '@nestjs/common';

@Injectable()
export class GithubHelper {
  private octokit: Octokit;
  private owner: string;
  private repo: string;
  private branch: string;

  constructor(private configService: ConfigService) {
    const accessToken = this.configService.get('GITHUB_TOKEN');
    this.owner = this.configService.get('GITHUB_OWNER');
    this.repo = this.configService.get('GITHUB_REPO');
    this.branch = this.configService.get('GITHUB_BRANCH');

    if (!accessToken || !this.owner || !this.repo) {
      throw new Error('Missing GitHub configuration in environment variables');
    }

    this.octokit = new Octokit({ auth: accessToken });
  }

  /**
   * Function to upload image
   */
  async uploadToRepository(folderPath: string, buffer: Buffer) {
    try {
      const fileData = buffer.toString('base64');

      await this.octokit.repos.createOrUpdateFileContents({
        owner: this.owner,
        repo: this.repo,
        path: folderPath,
        message: `Uploaded ${folderPath}`,
        content: fileData,
        branch: this.branch,
      });

      return {
        status: true,
        message: 'Image uploaded to GitHub repository successfully',
        fileResponse: {
          folderPath,
        },
      };
    } catch (error) {
      return {
        status: false,
        message: 'Error while uploading image to GitHub repository',
        fileResponse: error,
      };
    }
  }

  /**
   * Function to delete image from GitHub repository
   */
  async deleteFromRepository(filePath: string) {
    try {
      // Get the file SHA
      const response = await this.octokit.repos.getContent({
        owner: this.owner,
        repo: this.repo,
        path: filePath,
        ref: this.branch,
      });

      // Handle different response types
      const fileSha = Array.isArray(response.data)
        ? null
        : (response.data as { sha: string }).sha;

      if (!fileSha) {
        throw new Error(`File SHA not found for ${filePath}`);
      }

      // Delete the file
      await this.octokit.repos.deleteFile({
        owner: this.owner,
        repo: this.repo,
        path: filePath,
        message: `Deleted ${filePath}`,
        sha: fileSha,
        branch: this.branch,
      });

      return {
        status: true,
        message: 'File deleted from GitHub repository successfully.',
      };
    } catch (error) {
      console.error(`Error deleting file ${filePath}:`, error);
      throw new InternalServerErrorException(error);
    }
  }

  /**
   * Function to get the public URL for a file in the repository
   */
  async getFileUrl(filePath: string): Promise<string> {
    try {
      const response = await this.octokit.repos.getContent({
        owner: this.owner,
        repo: this.repo,
        path: filePath,
        ref: this.branch,
      });

      if (Array.isArray(response.data)) {
        throw new Error(`${filePath} is a directory, not a file`);
      }

      const fileUrl = response.data.download_url;
      return fileUrl;
    } catch (error) {
      console.error(`Error getting file URL for ${filePath}:`, error);
      throw new InternalServerErrorException(error);
    }
  }
}


Enter fullscreen mode Exit fullscreen mode

Open the image.controller.ts file and update it with the following code:

import { Controller, Post, UploadedFile, UseInterceptors } from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { UploadService } from './upload.service';

@Controller('image')
export class ImageController {
  constructor(private readonly uploadService: UploadService) {}

  @Post('upload')
  @UseInterceptors(FileInterceptor('file'))
  async uploadImage(@UploadedFile() file: Express.Multer.File) {
    return this.uploadService.uploadToGitHub(file.buffer, file.originalname);
  }
}
Enter fullscreen mode Exit fullscreen mode

Open the upload.service.ts file and add the following code:

import { GithubHelper } from '../../common/helper/github.helper';
import { GlobalHelper } from '../../common/helper/global.helper';

@Injectable()
export class uploadService{

async uploadToGitHub(file:any,payload:any){
        if (file) {
const extension = file.file.mimetype.split('/')[1];
          const fileName = `${payload.name}.${extension}`;
          const foodFolderPath = this.globalHelper.foodFolderPath(data.id);
          const totalPath = `${foodFolderPath}/${fileName}`;
          const uploadResult = await this.githubHelper.uploadToRepository(
            totalPath,
            file.file.buffer,
          );
          const url = await this.githubHelper.getFileUrl(totalPath);
       return {
        status:true,
        message:"image URL"
        data:{
          url,
         }
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

get the required environment variables from the .ENV file.

I hope this tutorial helps you understand the concept behind implementing a GitHub service. There are a few alternative approaches as well, but I found this method easier to understand and implement. Please feel free to share your feedback and suggestions.

Thank you for reading 🙏

If you enjoyed this article or found it helpful, give it a thumbs-up 👍

Top comments (3)

Collapse
 
silverium profile image
Soldeplata Saketos

I missed a study about the differences in performance and availability of both services. There is a reason people use AWS instead of GitHub

Collapse
 
rohan9607 profile image
rohan gaikwad

Excellent article, very helpful 👍

Collapse
 
hicongtuoc profile image
GNOL

Great article! Can you give me a reference to the repository?
Thank you