120

I need to check if a file exists using AWS SDK. Here is what I'm doing:

var params = {
    Bucket: config.get('s3bucket'),
    Key: path
};

s3.getSignedUrl('getObject', params, callback);

It works but the problem is that when the object doesn't exists, the callback (with arguments err and url) returns no error, and when I try to access the URL, it says "NoSuchObject".

Shouldn't this getSignedUrl method return an error object when the object doesn't exists? How do I determine if the object exists? Do I really need to make a call on the returned URL?

DᴀʀᴛʜVᴀᴅᴇʀ
  • 7,681
  • 17
  • 73
  • 127
Maurício Giordano
  • 3,116
  • 4
  • 32
  • 60
  • 1
    I'm sure there's a reason for not returning an error when the object doesn't exist. It sure is a pain in the butt! – Adrian Lynch May 19 '15 at 07:18
  • I couldn't believe we can't do this simple check without using Promise in some way. I need to do this inside AWS CDK and these approaches below aren't compatible with how the library works – Khoa May 20 '21 at 12:27
  • @Khoa in CDK you need to do async work outside the constructor and pass in the data you need via props – CaptEmulation Aug 22 '22 at 22:53

11 Answers11

183

Before creating the signed URL, you need to check if the file exists directly from the bucket. One way to do that is by requesting the HEAD metadata.

// Using callbacks
s3.headObject(params, function (err, metadata) {  
  if (err && err.name === 'NotFound') {  
    // Handle no object on cloud here  
  } else if (err) {
    // Handle other errors here....
  } else {  
    s3.getSignedUrl('getObject', params, callback);  
    // Do stuff with signedUrl
  }
});

// Using async/await
try {
  await s3.headObject(params).promise();
  const signedUrl = s3.getSignedUrl('getObject', params);
  // Do stuff with signedUrl
} catch (error) {
  if (error.name === 'NotFound') { // Note with v3 AWS-SDK use error.code
    // Handle no object on cloud here...
  } else {
    // Handle other errors here....
  }
}
CaptEmulation
  • 4,416
  • 2
  • 17
  • 14
  • 2
    @shadi You need read access to the object ([headObject docs](http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#headObject-property)), "Forbidden" means you don't have access to that object. – Marc Sep 22 '17 at 14:42
  • I think I had full access, but I'm not sure. It was so long ago – Shadi Sep 22 '17 at 14:45
  • @shadi I don't think so.... you might have had access to files but not the folders. – CaptEmulation Sep 22 '17 at 18:56
  • This is old news by now, but "Forbidden" definitely means you don't have the appropriate "read" access – Jacksonkr Nov 29 '17 at 16:40
  • 7
    For anyone else coming to this - it is an access issue but it's not obvious what access you need - it's actually `ListBucket` that's required https://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectHEAD.htm – James Lelyveld Jan 31 '18 at 13:56
  • how can we implement async await version of this code ? – NIKHIL C M Jun 05 '18 at 23:05
  • @NIKHILCM async/await version added. Completely untested. Use at your own risk. – CaptEmulation Jun 11 '18 at 12:37
  • 1
    In Async Await version, getSignedUrl....apparently does not need .promise() . Other curios thing that I'm not sure why if I perform only s3.getSignedUrl('getObject', params) it won't return the full url... seems that the call headObject it's necessary to get back the complete signed url... isn't it? – koalaok Nov 06 '19 at 10:49
  • @koalaok You are correct, `.promise` is not required and the answer has been edited. You can get a signedUrl for anything, even if it does not exist. The `headObject` check is to see if the object exists – CaptEmulation Dec 10 '19 at 21:57
  • @JamesLelyveld - according to https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#headObject-property ListBucket is not required. However, having the ListBucket permission will change the response in the case that the object does not exist from 403 (no ListBucket permission) to 404 (yes ListBucket permission). – FiveOFive Aug 25 '21 at 17:29
34

The simplest solution without try/catch block.

const exists = await s3
  .headObject({
    Bucket: S3_BUCKET_NAME,
    Key: s3Key,
  })
  .promise()
  .then(
    () => true,
    err => {
      if (err.code === 'NotFound') {
        return false;
      }
      throw err;
    }
  );
sky
  • 685
  • 7
  • 8
11

by using headObject method

AWS.config.update({
        accessKeyId: "*****",
        secretAccessKey: "****",
        region: region,
        version: "****"
    });
const s3 = new AWS.S3();

const params = {
        Bucket: s3BucketName,
        Key: "filename" //if any sub folder-> path/of/the/folder.ext
}
try {
        await s3.headObject(params).promise()
        console.log("File Found in S3")
    } catch (err) {
        console.log("File not Found ERROR : " + err.code)
}

As params are constant, the best way to use it with const. If the file is not found in the s3 it throws the error NotFound : null.

If you want to apply any operations in the bucket, you have to change the permissions of CORS Configuration in the respective bucket in the AWS. For changing permissions Bucket->permission->CORS Configuration and Add this code.

<CORSConfiguration>
<CORSRule>
    <AllowedOrigin>*</AllowedOrigin>
    <AllowedMethod>PUT</AllowedMethod>
    <AllowedMethod>POST</AllowedMethod>
    <AllowedMethod>DELETE</AllowedMethod>
    <AllowedMethod>GET</AllowedMethod>
    <AllowedMethod>HEAD</AllowedMethod>
    <AllowedHeader>*</AllowedHeader>
</CORSRule>
</CORSConfiguration>

for more information about CORS Configuration : https://docs.aws.amazon.com/AmazonS3/latest/dev/cors.html

Darin Kolev
  • 3,401
  • 13
  • 31
  • 46
banoth ravinder
  • 1,314
  • 15
  • 18
7

AFAICT the correct way to do this as of February 2022, with JavaScript V3 SDK is to use the HeadObjectCommand.

Note: I'm using TypeScript here with explicit typings, but you can remove those explicit typings when you refactor the code...they're just to show the AWS types in use.

import {
    S3Client,
    HeadObjectCommand, HeadObjectCommandInput, HeadObjectCommandOutput,
} from '@aws-sdk/client-s3';

function async existsInS3(
    client: S3Client,
    bucket: string,
    key: string,
): Promise<boolean> {
    try {
        const bucketParams: HeadObjectCommandInput = {
            Bucket: bucket,
            Key: key,
        };
        const cmd = new HeadObjectCommand(bucketParams);
        const data: HeadObjectCommandOutput = await client.send(cmd);

        // I always get 200 for my testing if the object exists
        const exists = data.$metadata.httpStatusCode === 200;
        return exists;
    } catch (error) {
        if (error.$metadata?.httpStatusCode === 404) {
            // doesn't exist and permission policy includes s3:ListBucket
            return false;
        } else if (error.$metadata?.httpStatusCode === 403) {
            // doesn't exist, permission policy WITHOUT s3:ListBucket
            return false;
        } else {
            // some other error
            ...log and rethrow if you like
        }
    }
}

If you look in the Permissions section of the HeadObjectCommand documentation linked above, you'll notice it mentions the 403 and 404 responses:

You need the relevant read object (or version) permission for this operation. For more information, see Specifying Permissions in a Policy. If the object you request does not exist, the error Amazon S3 returns depends on whether you also have the s3:ListBucket permission.

If you have the s3:ListBucket permission on the bucket, Amazon S3 returns an HTTP status code 404 ("no such key") error.

If you don’t have the s3:ListBucket permission, Amazon S3 returns an HTTP status code 403 ("access denied") error.

I don't know if these error responses could stem from other errors in addition to the non-existence of the key.

CORS

I also had to add HEAD in the AllowedMethods section of CORS permissions on the bucket:

"AllowedMethods": [
    "GET",
    "PUT",
    "HEAD"
],
wraiford
  • 181
  • 1
  • 5
3

This is based on AWS SDK for JavaScript v3.

import {
    S3Client,
} from "@aws-sdk/client-s3";

const s3Client = new S3Client({
    region: "your-aws-region"
});

const checkIfFileExist = async () => {
    try {
        const response = await s3Client.send(
            new HeadObjectCommand({
                Bucket: "your-bucket",
                Key: "your-file.txt"
            })
        );
        return true;
    } catch (error) {
        if (error.httpStatusCode === 404) return false;
    }
};

const doSomeProcess = async () => {
    const fileExist = await checkIfFileExist();
    console.log(fileExist);
}
nntona
  • 409
  • 4
  • 9
  • It looks like `error.httpStatusCode` doesn't exist in the sdk 3.171.0. Either use `error.$metadata.httpStatusCode === 404` or `error.errorType === "NotFound"` – Tim Van Laer Dec 02 '22 at 11:23
  • This is trapping any non-404 errors silently, which is probably both unintended and undesired. Also it is assuming internals of error like pertaining to Tim Van Laer's comment, so you may want to use safe navigation on the properties and re-throw the error via `throw error` as a catchall. – wraiford Jul 18 '23 at 10:40
2

Use getObject method like this:

var params = {
    Bucket: config.get('s3bucket'),
    Key: path
};
s3.getObject(params, function(err, data){
    if(err) {
        console.log(err);
    }else {
      var signedURL = s3.getSignedUrl('getObject', params, callback);
      console.log(signedURL);
   }
});
Amaynut
  • 4,091
  • 6
  • 39
  • 44
2

You can also use the waitFor method together with the state objectExists. This will use S3.headObject() internally.

var params = {
  Bucket: config.get('s3bucket'),
  Key: path
};
s3.waitFor('objectExists', params, function(err, data) {
  if (err) console.log(err, err.stack); // an error occurred
  else     console.log(data);           // successful response
});
disco crazy
  • 31,313
  • 12
  • 80
  • 83
2

An alternative way to do this which isn't mentioned in the other answers is to use the listObjectsV2 command.

I don't know if this is better than the headObject approach, but it doesn't rely on 404 responses and try/catches.

The trick is to set the Prefix param to match the key you are looking for.

async function checkObjectExists(bucket, key) {
    const data = await s3
        .listObjectsV2({
            Bucket: bucket,
            MaxKeys: 1, // We should only get 1 object, or 0.
            Prefix: key, // Limit the response to objects whose key starts with the given key.
        })
        .promise();
    return data.Contents.length > 0;
}

vaughandroid
  • 4,315
  • 1
  • 27
  • 33
1

Promise.All without failure Synchronous Operation

var request = require("request");
var AWS = require("aws-sdk");

AWS.config.update({
    accessKeyId: "*******",
    secretAccessKey: "***********"
});


const s3 = new AWS.S3();


var response;

function initialize(bucket,key) {
    // Setting URL and headers for request
    const params = {
        Bucket: bucket,
        Key: key
    };
    // Return new promise 
    return new Promise(function(resolve, reject) {
        s3.headObject(params, function(err, resp, body) {  
            if (err) {  
                resolve(key+"/notfound");
            } else{
                resolve(key+"/found");
            }
          })
    })
}

function main() {

    var foundArray = new Array();
    var notFoundArray = new Array();
    var prefix = 'abc/test/';
    var promiseArray = [];
    try{
    for(var i=0;i<10;i++)
    {
        var key = prefix +'1234' + i;
        console.log("Key : "+ key);
        promiseArray[i] = initialize('bucket',key);
        promiseArray[i].then(function(result) {
            console.log("Result : " + result);
            var temp = result.split("/");
            console.log("Temp :"+ temp);
            if (temp[3] === "notfound")
            {
                console.log("NOT FOUND");
            }else{
                console.log("FOUND");
            }

        }, function(err) {
            console.log (" Error ");
        });
    }

    Promise.all(promiseArray).then(function(values) {
        console.log("^^^^^^^^^^^^TESTING****");
      }).catch(function(error) {
          console.error(" Errro : "+ error);
      });




}catch(err){
    console.log(err);
}


}

main();
ABHAY JOHRI
  • 1,997
  • 15
  • 19
0

Synchronous call on S3 in Nodejs instead of asynchronous call using Promise

var request = require("request");
var AWS = require("aws-sdk");

AWS.config.update({
    accessKeyId: "*****",
    secretAccessKey: "********"
});


const s3 = new AWS.S3();


var response;

function initialize(bucket,key) {
    // Setting URL and headers for request
    const params = {
        Bucket: bucket,
        Key: key
    };
    // Return new promise 
    return new Promise(function(resolve, reject) {
        s3.headObject(params, function(err, resp, body) {  
            if (err) {  
                console.log('Not Found : ' + params.Key );
                reject(params.Key);
            } else {  
                console.log('Found : ' + params.Key );
                resolve(params.Key);
            }
          })
    })
}

function main() {

    var foundArray = new Array();
    var notFoundArray = new Array();
    for(var i=0;i<10;i++)
    {
        var key = '1234'+ i;
        var initializePromise = initialize('****',key);
        initializePromise.then(function(result) {
            console.log('Passed for : ' + result);
            foundArray.push(result);
            console.log (" Found Array : "+ foundArray);
        }, function(err) {
            console.log('Failed for : ' + err);
            notFoundArray.push(err);
            console.log (" Not Found Array : "+ notFoundArray);
        });
    }


}

main();
ABHAY JOHRI
  • 1,997
  • 15
  • 19
0

Synchronous Put Operation

var request = require("request");
var AWS = require("aws-sdk");

AWS.config.update({
    accessKeyId: "*****",
    secretAccessKey: "***"
});


const s3 = new AWS.S3();


var response;

function initialize(bucket,key) {
    // Setting URL and headers for request
    const params = {
        Bucket: bucket,
        Key: key
    };
    // Return new promise 
    return new Promise(function(resolve, reject) {
        s3.putObject(params, function(err, resp, body) {  
            if (err) {  
                reject();
            } else {  
                resolve();
            }
          })
    })
}

function main() {

    var promiseArray = [];
    var prefix = 'abc/test/';
    for(var i=0;i<10;i++)
    {
        var key = prefix +'1234'+ i;
        promiseArray[i] = initialize('bucket',key);
        promiseArray[i].then(function(result) {
            console.log (" Successful ");
        }, function(err) {
            console.log (" Error ");
        });
    }


      console.log('Promises ' + promiseArray);


    Promise.all(promiseArray).then(function(values) {
        console.log("******TESTING****");
      });


}


main();
ABHAY JOHRI
  • 1,997
  • 15
  • 19