In this article, we are going to look at simple Go APIs to deal with local files using io/ioutil package

Working with files can be tough and I am saying this from my own experience. The thing I love about Node.js is its simplicity. The built-in fs module of Node comes with rich API to deal with local file-system but it also provides simple APIs to work with files.
However, Go has many packages that deal with the file-system and IO. While working with files and data streams, you may consume APIs from the os package and io package. These APIs sometimes could be difficult to work with and one needs to have in-depth knowledge of how file-system works.
In most cases, we just want to perform simple read-write operations and we don’t want to deal with file descriptors and permissions. In a nutshell, we want to abstract ourselves from the complicated internal implementation.
The io/ioutil package provides simple utility functions to deal with files without having to worry too much about internal implementation. Let’s take look at some of the important functions exported this package.
I have created a temporary directory at /Users/Uday.Hiwarale/tmp location for the demonstration of these functions and dump some sample files.
/Users/Uday.Hiwarale/tmp
├── .htpasswd
├── files
| └── test.pdf
├── main.js
├── page.html
└── style.css
ReadDir function
The ioutil.ReadDir function returns the information about files contained in a directory. This function has the below syntax.
func ReadDir(dirname string) ([]os.FileInfo, error)
This function takes a string argument which is the path of the directory and returns a list of FileInfo objects. The FileInfo interface defines some important methods to get information about the file.
type FileInfo interface {
Name() string // base name of the file
Size() int64 // length in bytes
Mode() FileMode // file mode bits
ModTime() time.Time // modification time
IsDir() bool // abbreviation for Mode().IsDir()
Sys() interface{} // underlying data source (can return nil)
}
Let’s write a simple program to list the content of the /tmp directory.

The program above yields the below result.
Name: .htpasswd, Size: 0 kb, Mode: -rw-r--r--, IsDir: false
Name: files, Size: 0 kb, Mode: drwxr-xr-x, IsDir: true
Name: index.html, Size: 0 kb, Mode: -rw-r--r--, IsDir: false
Name: main.js, Size: 0 kb, Mode: -rw-r--r--, IsDir: false
Name: style.css, Size: 0 kb, Mode: -rw-r--r--, IsDir: fals
I apologize for going off the topic here but if you need to list the files in a directory filtered by a glob pattern, then you should use filepath.Glob() function which returns the list of file paths.
func Glob(pattern string) (matches []string, err error)
Then you can use os.Stat or os.Lstat function to get the FileInfo object from the filepath.
func Stat(filepath string) (FileInfo, error)

ReadFile function
To read a file using a file path, we can use the ioutil.ReadFile function. This function returns the content of the file as an array of bytes.
func ReadFile(filepath string) ([]byte, error)

The above program yields the result below.
Raw:
[60 104 116 109 108 32 108 97 110 103 61 39 101 110 39 62 10 32 32 32 32 60 104 101 97 100 62 10 32 32 32 32 32 32 32 32 60 109 ...]
String:
<html lang='en'>
<head>
<meta charset='UTF-8'>
<meta http-equiv='X-UA-Compatible' content='ie=edge'>
<title>Golang</title>
</head>
<body>
<h1>Hello World!</h1>
</body>
</html>
💡 You can also use the fmt.Printf function with %s formatting operator to convert binary data into UTF-8 character representation.
WriteFile function
To simply write some binary data to a file, we can use ioutil.WriteFile function. This function has the below syntax.
func WriteFile(filepath string, data []byte, perm os.FileMode) error
The one odd thing about this function compared to ReaFile() is the perm argument. This is the Unix permission bits of chmod we are familiar with. You can visit this online calculator to calculate permission bits.
If a file with the filepath does not exist, it will be created. If the file already exists, the file’s content will be wiped out (truncated) before writing new content.

The only problem with ioutil.WriteFile() is that it can not create a nested file if the parent directory does not exist. For example, if the file path is /tmp/docs/welcome.txt then new file creation fails because /tmp/docs directory does not exist.
In such a situation, we should use os.MkdirAll function which creates a nested directory. This is much like a mkdir -p command in Bash. If the directory already exists, this function does nothing and safely exits.
The ioutil package also provides TempDir and TempFile functions to work with temporary files which can be useful if you want to process some files before putting them on the disk where users can see and use them.
func TempDir(dir, prefix string) (string, error)
func TempFile(dir, pattern string) (*os.File, error)
This package also provides ReadAll method to collect the content of a data stream and deliver it at once. Since we are not focusing on the data streams in this article, we can have a look at this method some other time.
func ReadAll(r io.Reader) ([]byte, error)

Working with files using ioutil package was originally published in RunGo on Medium, where people are continuing the conversation by highlighting and responding to this story.