How do i create a image hosting website like imgur with less costs?

i want to POS my service, i want to provide image hosting service for my customers where my images will be like example.com/image_id.jpg

how do i achieve the custom image url with another image hosting service like imgur until i have POS?
i have checked this question but , there isnt any answer yet on it

please help me to get my POS where my images are hosted on my domain , i want to the costs to be free until i have a POS.

What is your website skill level? What have you learned so far?

The most cost effective means of providing a low cost web experience without sacrificing flexibility, control, scale or performance is to take the server out of equation. No server(s) to manage no costs. Instead replacing the server with web experience hosted in the cloud on a CDN. Instead of using a relational database use other alternative lower-cost data storage cloud solutions like AWS s3, AWS App Sync, Open Search, DynamoDB, Athena, etc. All of these services living in the cloud can be communicated with directly in the browser using zero-trust signed urls. This eliminates the need to create, manage, and instrument code separate from the one running the web experience. The web experience itself ui is build using JavaScript. Highly recommended using a powerful modern web app framework like Angular to build those ui experiences. There are others like React and Vue but I would highly recommend Angular due to many of reasons mentioned in the below article.

https://sam-redmond.com/why-i-always-use-angular-5efcf31d739a

Specifically now to your question I would create a Angular application that uploads files directly to s3. That application would be hosted on s3 as well in a private bucket. The app would be served through amazons CDN cloud front. An app like that in free tier would probably run for a long time for free and even as it scales would be very low cost like dollars a month at most.

The reason I recommend aws is not only because of my familiarity with it but also because the signed v4 request architecture compatible with ALL AWS services can truly be leveraged to communicate securely with AWS services in the browser alone. This is the key to removing the server completely from the app dev equation and significantly reducing costs all around from infrastructure to personel.

Building these types of applications quickly that can run on near 0 cost cloud architecture is a primary objective of the project I have been developing.

The most excessive cost in my current stack is Open Search Service which can also be reduced using AWS Athena instead for my purposes of dynamic route strategy for mapping app routes to components for rendering.

My app currently uploads images to s3. However, it does through an older rest api I built in go. That will soon be replaced by a in browser zero-trust call to s3 api. Eliminating the need for the separate api and moving more towards a pure server-less application.

This entire architecture can run any application at significantly lower cost not only in terms of infrastrcuture but personel as well. When compared to a legacy php app like wordpress. Especially if that pile of grabage needs to scale. Scaling a php application requires adding more nodes and possibly for cpu. All that comes at a cost. That can be completely eliminated using rhe approach above I described taking the server out of the equation. Running a static application from a cdn cost nothing in comparison to running a wordpress website which requires a variable amount of costly infrastrcture based on traffic needs.

Anyway to sum up

  • Deliver web experience as static site hosted on CDN
  • Persist data to cloud based pay for usage modeled storages not MySQL
  • Eliminate servers by securely communicating with services in cloud within browser alone.

Added bonus

  • Pick storages that are highly available, scalable, low cost , performant out of the box NOT MySQL

developer happiness

  • Store data as natural form in JSON ideally NOT across a complex hierarchy of relational tables
  • Allow developers to focus on experiences not building repetitive rest APIs and building/managing infrastrcture.

Those parties interested this is TypeScript code for a data adaptor part of my project that communicates with AWS s3 securely directly in the browser alone. No middle man, no extra server node /cpu needed nothing. Just direct to s3 using zero-trust singed v4 urls in the browser alone. This one does’t upload upload binary documents it uloads data entities as json documents. This replaces something like MySQL were the entity would be spread across a sewer of tables.

import { CognitoIdentityClient } from '@aws-sdk/client-cognito-identity';
import { GetObjectCommand, PutObjectCommand, S3Client } from '@aws-sdk/client-s3';
import { fromCognitoIdentityPool } from '@aws-sdk/credential-provider-cognito-identity';
import { AuthFacade } from 'auth';
import { CognitoSettings } from 'awcog';
import { CrudAdaptorPlugin, CrudOperationResponse, CrudOperationInput, CrudCollectionOperationInput, CrudCollectionOperationResponse } from 'crud';
import { Param, ParamEvaluatorService } from 'dparam';
import { AllConditions, AnyConditions, ConditionProperties } from 'json-rules-engine';
import { firstValueFrom, forkJoin, from, iif, Observable, of } from 'rxjs';
import { catchError, map, switchMap, take, tap } from 'rxjs/operators';
import * as uuid from 'uuid';
import { SignatureV4} from "@aws-sdk/signature-v4";
import { HttpRequest } from "@aws-sdk/protocol-http";
import { Sha256 } from "@aws-crypto/sha256-js";
import { HttpClient } from '@angular/common/http';
import { isPlatformServer } from '@angular/common';
import { AsyncApiCallHelperService } from 'utils';

export const s3EntityCrudAdaptorPluginFactory = (platformId: Object, authFacade: AuthFacade, cognitoSettings: CognitoSettings, paramsEvaluatorService: ParamEvaluatorService, http: HttpClient, asyncApiCallHelperSvc: AsyncApiCallHelperService, hostName?: string, protocol?: string) => {
  return new CrudAdaptorPlugin<string>({
    id: 'aws_s3_entity',
    title: 'AWS S3 Entity',
    create:  ({ }: CrudOperationInput) => of<CrudOperationResponse>({ success: false }),
    read: ({ }: CrudOperationInput) => of<CrudOperationResponse>({ success: false }),
    update: ({ object, identity, params }: CrudOperationInput) => of({ success: false }).pipe(
      map(() => buildClient(authFacade, cognitoSettings)),
      switchMap(s3 => identity({ object }).pipe(
        map(({ identity }) => ({ s3, identity }))
      )),
      switchMap(({ s3, identity }) => params && Object.keys(params).length !== 0 ? forkJoin(Object.keys(params).map(name => paramsEvaluatorService.paramValue(params[name], new Map<string, any>()).pipe(map(v => ({ [name]: v }))))).pipe(
        map(groups => groups.reduce((p, c) => ({ ...p, ...c }), {})), // default options go here instead of empty object.
        map(options => ({ s3, identity, options }))
      ): of({ s3, identity, options: {} })),
      map(({ s3, identity, options }) => {
        const name = options.prefix + identity + '.json';
        const command = new PutObjectCommand({
          Bucket: options.bucket,
          Key: name,
          Body: JSON.stringify(object),
          ContentType: 'application/json',
          CacheControl: `ETag: ${uuid.v4()}` // cache could be part of adaptor options - for now KISS
        });
        return { s3, command };
      }),
      switchMap(({ s3, command }) => new Observable<CrudOperationResponse>(obs => {
        s3.send(command).then(res => {
          console.log('sent');
          console.log(res);
          obs.next({ success: true });
          obs.complete();
        }).catch(e => {
          console.log('error')
          console.log(e);
          obs.next({ success: false })
          obs.complete();
        });
      }))
    ),
    delete: ({ }: CrudOperationInput) => of<CrudOperationResponse>({ success: false }),
    query: ({ rule, params }: CrudCollectionOperationInput) => of({ entities: [], success: false }).pipe(

      // This can be moved into crud adaptor call to shared method inside dparams probably.
      switchMap(() => iif(
        () => Object.keys(params).length > 1,
        forkJoin(
          Object.keys(params).map(name => paramsEvaluatorService.paramValue(params[name], new Map<string, any>()).pipe(
            map(v => ({ [name]: v }))
          ))
        ).pipe(
          map(groups => groups.reduce((p, c) => ({ ...p, ...c }), {})), // default options go here instead of empty object.
          map(options => ({ options }))         
        ),
        iif(
          () => Object.keys(params).length !== 0,
          paramsEvaluatorService.paramValue(Object.keys(params).length !== 0 ? params[Object.keys(params)[0]] : new Param(), new Map<string, any>()).pipe(
            map(optionValue => ({ options: { [Object.keys(params)[0]]: optionValue } }))
          ),
          of({ options: {} })
        )
      )),
      // This can be moved into crud adaptor and passed as argument.
      map(({ options }) => ({ options, identityCondition: (rule.conditions as AllConditions).all.map(c => (c as AnyConditions).any.find(c2 => (c2 as ConditionProperties).fact === 'identity')).find(c => !!c) })),

      switchMap(({ identityCondition, options }) => iif(

        () => identityCondition !== undefined && (identityCondition as ConditionProperties).fact === 'identity',

        // This could probably be moved into an aw util module to easily build rest queries for any aw service efficently.
        createS3SignedHttpRequest({
          method: "GET",
          body: undefined,
          headers: {
            "Content-Type": "application/json",
            host: `${(options as any).bucket}.s3.amazonaws.com`,
          },
          hostname: `${(options as any).bucket}.s3.amazonaws.com`,
          path: `${(options as any).prefix}${(identityCondition as ConditionProperties).value}.json`,
          protocol: 'https:',
          service: "s3",
          cognitoSettings: cognitoSettings,
          authFacade: authFacade
        }).pipe(
          tap(() => console.log('.marker({ event: AFTER , entity: s3 , op: query , meta: { action: createSignedRequest } })')),
          tap(signedHttpRequest => delete signedHttpRequest.headers.host),
          map(signedHttpRequest => ({ signedHttpRequest, options, url: `${ isPlatformServer(platformId) ? /*'http://localhost:4000'*/ `${protocol}://${hostName}` /*`https://${options.bucket}.s3.amazonaws.com`*/ : '' }${ `/awproxy/s3/${(options as any).bucket}` }${signedHttpRequest.path}` })),
          tap(() => console.log('.marker({ event: BEFORE , entity: crud , op: query , meta: { http: get } })')),
          switchMap(({ signedHttpRequest, url }) => http.get(url, { headers: signedHttpRequest.headers, withCredentials: true })),
          tap(() => console.log('.marker({ event: AFTER , entity: s3 , op: query , meta: { http: get } })')),
          map(res => ({ entities: res ? [ res ] : [], success: res ? true : false }))
        ),

        of({ entities: [], success: false })

      ))
    )
  });
};

// This will no longer be needed once write op sdk usage is replaced by signed url
const buildClient = (authFacade: AuthFacade, cognitoSettings: CognitoSettings) => new S3Client({
  region: cognitoSettings.region,
  credentials: fromCognitoIdentityPool({
    client: new CognitoIdentityClient({ region: cognitoSettings.region }),
    identityPoolId: cognitoSettings.identityPoolId,
    logins: {
      [`cognito-idp.${cognitoSettings.region}.amazonaws.com/${cognitoSettings.userPoolId}`]: () => firstValueFrom(authFacade.getUser$.pipe(map(u => u ? u.id_token : undefined)))
    }
  }),
});

// This should all be moved in either aw util module or awsig module.
// Could also decouple signature from vendor for reuse accross vendors that signed v4 urls can be used.
interface CreateSignHttpRequestParams {
  body?: string;
  headers?: Record<string, string>;
  hostname: string;
  method?: string;
  path?: string;
  port?: number;
  protocol?: string;
  query?: Record<string, string>;
  service: string;
  cognitoSettings: CognitoSettings,
  authFacade: AuthFacade
}

const createS3SignedHttpRequest = ({
  body,
  headers,
  hostname,
  method = "GET",
  path = "/",
  port = 443,
  protocol = "https:",
  query,
  service,
  cognitoSettings,
  authFacade
}: CreateSignHttpRequestParams): Observable<HttpRequest> => of(
  new HttpRequest({
    body,
    headers,
    hostname,
    method,
    path,
    port,
    protocol,
    query,
  }
)).pipe(
  tap(() => console.log('.marker({ event: BEGIN , context: s3, entity: sig , op: signv4 , meta: {  } })')),
  switchMap(req => from(
    (new SignatureV4(
      {
        credentials: fromCognitoIdentityPool({
          client: new CognitoIdentityClient({ region: cognitoSettings.region }),
          identityPoolId: cognitoSettings.identityPoolId,
          logins: {
            [`cognito-idp.${cognitoSettings.region}.amazonaws.com/${cognitoSettings.userPoolId}`]: () => firstValueFrom(authFacade.getUser$.pipe(map(u => u ? u.id_token : undefined)))
          }
        }),
        region: cognitoSettings.region,
        service,
        sha256: Sha256,
      }
    )).sign(req)
      .then(
        signedReq => {
          console.log('.marker({ event: RESOLVED, entity: s3 , op: signv4 , meta: {  } })');
          return signedReq;
        }
      )
  ).pipe(
    tap(() => console.log('.marker({ /s3/sign/after/sig })')),
    take(1)
  )),
  map(req => req as HttpRequest),
  tap(() => console.log('.marker({ event: END , context: s3, entity: sig , op: signv4 , meta: {  } })')),
);
2 Likes

@WebSteve i know MERN stack very well, i have worked on backend and frontend too. i have used cloudinary before for image hosting

@windbeneathmywings , i dont have my AWS free tier now, and i cant chose it : ( . and AWS needs credit card ,but i am still a student, and for POC of my application, trying to use AWS without earning any money is very risky .

and to beat the competition with famous service called imgur, i have to provide atleast 10k requests per month , or i need to give some extra features like making the images private for free. like domain level access.

I tried looking for other services like cloudfare cdn images, but that costs 5$ per month for 100K images, imagekit.io is free, but i want my custom domain but it has its own domain on the image.

are they no open source service ? to do my POC ?

I don’t understand? You should be able to use the free tier unless you have previously used AWS. In that case you could just create another account tied to a different email. Using AWS isn’t very risky if you limit resources being used and monitor costs. You can even set up budgets in the console and be notified when certain thresholds are met. I haven’t used budgets in AWS but I do try to monitor costs everyday. One of my accounts is outside the free tier because I have had it for 2 years. If it wasn’t for open search node in that account my costs would be about 3 – 5 dollars a month. One warning that I learned the hard way is creating vpc end-points is expensive. Eliminating that is one key factor in low cost architecture. Also eliminating the need for nodes in a vpc all together. Cpu compute time always on is a huge cost especially if you leave nodes up and running when not being used. Server-less computing plays a key role in that.

https://aws.amazon.com/aws-cost-management/aws-budgets/

https://www.cloudflare.com/learning/serverless/serverless-vs-containers/

The cloudflare doc above does a great job explaining serverless architecture. However, I would recommend using AWS instead.

I would also recommend serverless framework.

https://www.serverless.com/

Serverless framework was heavily used in my nearly now obsolete rest api in go. I only use it now for bundling and pushing the web app to aws easily as a lambda so that server-side rendering can be used alongside the cdn assets.

https://www.serverless.com/

please recommend any alternative for AWS. i dont want to give my Dads credit card details until i see a potential in my app , till then i want to use free resources. AWS like applications can charge money on credit card anytime, it is difficult to control them.

Without wishing to be rude, how old are you? If you are relying on your father’s credit card for purchases, are you old enough to legally enter into a contract with customers?

You could probably quickly build an upload clone site using this for free and deploy the pages created to a github pages site. The database though would be tricky to host without cost.

https://www.radzen.com/

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.