diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..d4f917d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+target
+Cargo.lock
+*.swp
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..a0ddc18
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+name = "jfs"
+version = "0.1.0"
+authors = ["Markus Kohlhase <mail@markus-kohlhase.de>"]
+license = "MIT"
+homepage = "https://github.com/flosse/rust-json-file-store"
+repository = "https://github.com/flosse/rust-json-file-store"
+description = "A JSON file store"
+keywords = ["json", "file", "store", "db", "database"]
+
+[dependencies]
+uuid = { version = "0.2", features = ["v4"] }
+rustc-serialize = "0.3"
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..e7a36ea
--- /dev/null
+++ b/README.md
@@ -0,0 +1,20 @@
+# JSON file store
+
+A simple JSON file store written in Rust.
+This is a port and drop-in replacement of the Node.js library
+[json-file-store](https://github.com/flosse/json-file-store/).
+
+WARNING:
+Don't use it if you want to persist a large amount of objects.
+Use a real DB instead.
+
+## Usage
+
+Add the following to your `Cargo.toml`
+
+    [dependencies]
+    jfs = "0.1"
+
+## License
+
+This project is licensed under the MIT License.
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..53e76a6
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,510 @@
+// Copyright (c) 2016 Markus Kohlhase <mail@markus-kohlhase.de>
+
+//! A simple JSON file store written in Rust.
+//! This is a port and drop-in replacement of the Node.js library
+//! [json-file-store](https://github.com/flosse/json-file-store/).
+//!
+//! **WARNING**:
+//! Don't use it if you want to persist a large amount of objects.
+//! Use a real DB instead.
+//!
+//! # Example
+//!
+//! ```
+//! extern crate jfs;
+//! extern crate rustc_serialize;
+//! use jfs::Store;
+//!
+//! #[derive(RustcEncodable,RustcDecodable)]
+//! struct Foo {
+//!   foo: String
+//! }
+//!
+//! pub fn main() {
+//!    let db = Store::new("data").unwrap();
+//!    let f = Foo { foo: "bar".to_owned() };
+//!    let id = db.save(&f).unwrap();
+//!    let obj = db.get::<Foo>(&id).unwrap();
+//!    db.delete(&id).unwrap();
+//! }
+//! ```
+
+extern crate uuid;
+extern crate rustc_serialize;
+
+use std::io::prelude::*;
+use std::io::{Error, ErrorKind, Result};
+use uuid::Uuid;
+use rustc_serialize::{Decodable, Encodable};
+use rustc_serialize::json::{self, Json};
+use std::path::PathBuf;
+use std::fs::{read_dir, rename, create_dir_all, remove_file, File, metadata};
+use std::collections::BTreeMap;
+
+#[derive(Clone,Copy)]
+pub struct Config {
+    pretty: bool,
+    indent: u32,
+    single: bool,
+}
+
+impl Default for Config {
+    fn default() -> Config {
+        Config {
+            indent: 2,
+            pretty: false,
+            single: false,
+        }
+    }
+}
+
+pub struct Store {
+    path: PathBuf,
+    cfg: Config,
+}
+
+impl Store {
+    fn id_to_path(&self, id: &str) -> PathBuf {
+        if self.cfg.single {
+            self.path.clone()
+        } else {
+            self.path.join(id).with_extension("json")
+        }
+    }
+
+    fn path_buf_to_id(&self, p: PathBuf) -> Result<String> {
+        p.file_stem()
+            .and_then(|n| n.to_os_string().into_string().ok())
+            .ok_or(Error::new(ErrorKind::Other, "invalid id"))
+    }
+
+    fn save_object_to_file<T: Encodable>(&self, obj: &T, file_name: &PathBuf) -> Result<()> {
+        let json_string = if self.cfg.pretty {
+            json::as_pretty_json(&obj).indent(self.cfg.indent).to_string()
+        } else {
+            try!(json::encode(&obj).map_err(|err| Error::new(ErrorKind::InvalidData, err)))
+        };
+
+        let tmp_filename = file_name.with_extension("tmp");
+
+        let mut file = try!(File::create(&tmp_filename));
+
+        match Write::write_all(&mut file, json_string.as_bytes()) {
+            Err(err) => Err(err),
+            Ok(_) => {
+                try!(rename(tmp_filename, file_name));
+                Ok(())
+            }
+        }
+    }
+
+    fn get_json_from_file(file_name: &PathBuf) -> Result<Json> {
+        let mut f = try!(File::open(file_name));
+        let mut buffer = String::new();
+        try!(f.read_to_string(&mut buffer));
+        json::Json::from_str(&buffer).map_err(|err| Error::new(ErrorKind::Other, err))
+    }
+
+    fn get_object_from_json(json: &Json) -> Result<&json::Object> {
+        json.as_object().ok_or(Error::new(ErrorKind::InvalidData, "invalid file content"))
+    }
+
+    pub fn new(name: &str) -> Result<Store> {
+        Store::new_with_cfg(name, Config::default())
+    }
+
+    pub fn new_with_cfg(name: &str, cfg: Config) -> Result<Store> {
+
+        let mut s = Store {
+            path: name.into(),
+            cfg: cfg,
+        };
+
+        if cfg.single {
+            s.path = s.path.with_extension("json");
+            let o = json::Object::new();
+            try!(s.save_object_to_file(&o, &s.path));
+        } else {
+            try!(create_dir_all(&s.path));
+        }
+        Ok(s)
+    }
+
+    pub fn save<T: Encodable>(&self, obj: &T) -> Result<String> {
+        self.save_with_id(obj, &Uuid::new_v4().to_string())
+    }
+
+    pub fn save_with_id<T: Encodable>(&self, obj: &T, id: &str) -> Result<String> {
+        if self.cfg.single {
+
+            let json = try!(Store::get_json_from_file(&self.path));
+            let o = try!(Store::get_object_from_json(&json));
+            let mut x = o.clone();
+
+            // start dirty
+            let s = try!(json::encode(&obj).map_err(|err| Error::new(ErrorKind::InvalidData, err)));
+            let j = try!(Json::from_str(&s).map_err(|err| Error::new(ErrorKind::InvalidData, err)));
+            // end dirty
+
+            x.insert(id.to_string(), j);
+            try!(self.save_object_to_file(&x, &self.path));
+
+        } else {
+            try!(self.save_object_to_file(obj, &self.id_to_path(id)));
+        }
+        Ok(id.to_owned())
+    }
+
+    pub fn get<T: Decodable>(&self, id: &str) -> Result<T> {
+        let json = try!(Store::get_json_from_file(&self.id_to_path(id)));
+        let o = if self.cfg.single {
+            let x = try!(json.find(id).ok_or(Error::new(ErrorKind::NotFound, "no such object")));
+            x.clone()
+        } else {
+            json
+        };
+
+        T::decode(&mut json::Decoder::new(o)).map_err(|err| Error::new(ErrorKind::Other, err))
+    }
+
+    pub fn get_all<T: Decodable>(&self) -> Result<BTreeMap<String, T>> {
+        if self.cfg.single {
+            let json = try!(Store::get_json_from_file(&self.id_to_path("")));
+            let o = try!(Store::get_object_from_json(&json));
+            let mut result = BTreeMap::new();
+            for x in o.iter() {
+                let (k, v) = x;
+                match T::decode(&mut json::Decoder::new(v.clone())) {
+                    Ok(r) => {
+                        result.insert(k.clone(), r);
+                    }
+                    Err(_) => {}
+                }
+            }
+            Ok(result)
+        } else {
+            let meta = try!(metadata(&self.path));
+            if !meta.is_dir() {
+                Err(Error::new(ErrorKind::NotFound, "invalid path"))
+            } else {
+                let entries = try!(read_dir(&self.path));
+                Ok(entries.filter_map(|e| {
+                        e.and_then(|x| {
+                                x.metadata().and_then(|m| {
+                                    if m.is_file() {
+                                        self.path_buf_to_id(x.path())
+                                    } else {
+                                        Err(Error::new(ErrorKind::Other, "not a file"))
+                                    }
+                                })
+                            })
+                            .ok()
+                    })
+                    .filter_map(|id| match self.get(&id) {
+                        Ok(x) => Some((id.clone(), x)),
+                        _ => None,
+                    })
+                    .collect::<BTreeMap<String, T>>())
+            }
+        }
+    }
+
+    pub fn delete(&self, id: &str) -> Result<()> {
+        if self.cfg.single {
+            let json = try!(Store::get_json_from_file(&self.path));
+            let o = try!(Store::get_object_from_json(&json));
+            let mut x = o.clone();
+            if x.contains_key(id) {
+                x.remove(id);
+            } else {
+                return Err(Error::new(ErrorKind::NotFound, "no such object"));
+            }
+            self.save_object_to_file(&x, &self.path)
+        } else {
+            remove_file(self.id_to_path(id))
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use std::io::prelude::*;
+    use std::fs::{remove_dir_all, File, remove_file};
+    use Store;
+    use Config;
+    use std::collections::BTreeMap;
+    use std::io::{Result, ErrorKind};
+    use std::path::Path;
+
+    static TEST_DIR_NAME: &'static str = ".specTests";
+    static TEST_FILE_NAME: &'static str = ".specTests.json";
+
+    #[derive(RustcEncodable,RustcDecodable)]
+    struct X {
+        x: u32,
+    }
+
+    #[derive(RustcEncodable,RustcDecodable)]
+    struct Y {
+        y: u32,
+    }
+
+    fn write_to_test_file(content: &str) {
+        let mut file = File::create(&TEST_FILE_NAME).unwrap();
+        Write::write_all(&mut file, content.as_bytes()).unwrap();
+    }
+
+    fn read_from_test_file() -> String {
+        let mut f = File::open(TEST_FILE_NAME).unwrap();
+        let mut buffer = String::new();
+        f.read_to_string(&mut buffer).unwrap();
+        buffer
+    }
+
+    fn teardown() -> Result<()> {
+        try!(match remove_file(TEST_FILE_NAME) {
+            Err(err) => {
+                match err.kind() {
+                    ErrorKind::NotFound => Ok(()),
+                    _ => Err(err),
+                }
+            }
+            Ok(_) => Ok(()),
+        });
+        match remove_dir_all(TEST_DIR_NAME) {
+            Err(err) => {
+                match err.kind() {
+                    ErrorKind::NotFound => Ok(()),
+                    _ => Err(err),
+                }
+            }
+            Ok(_) => Ok(()),
+        }
+    }
+
+    #[test]
+    fn save() {
+        let db = Store::new(TEST_DIR_NAME).unwrap();
+        #[derive(RustcEncodable)]
+        struct MyData {
+            x: u32,
+        };
+        let data = MyData { x: 56 };
+        let id = db.save(&data).unwrap();
+        let mut f = File::open(format!("{}/{}.json", TEST_DIR_NAME, id)).unwrap();
+        let mut buffer = String::new();
+        f.read_to_string(&mut buffer).unwrap();
+        assert_eq!(buffer, "{\"x\":56}");
+        assert!(teardown().is_ok());
+    }
+
+    #[test]
+    fn save_empty_obj() {
+        let db = Store::new(TEST_DIR_NAME).unwrap();
+        #[derive(RustcEncodable)]
+        struct Empty {};
+        let id = db.save(&Empty {}).unwrap();
+        let mut f = File::open(format!("{}/{}.json", TEST_DIR_NAME, id)).unwrap();
+        let mut buffer = String::new();
+        f.read_to_string(&mut buffer).unwrap();
+        assert_eq!(buffer, "{}");
+        assert!(teardown().is_ok());
+    }
+
+    #[test]
+    fn save_with_id() {
+        let db = Store::new(TEST_DIR_NAME).unwrap();
+        #[derive(RustcEncodable)]
+        struct MyData {
+            y: i32,
+        };
+        let data = MyData { y: -7 };
+        db.save_with_id(&data, "foo").unwrap();
+        let mut f = File::open(format!("{}/foo.json", TEST_DIR_NAME)).unwrap();
+        let mut buffer = String::new();
+        f.read_to_string(&mut buffer).unwrap();
+        assert_eq!(buffer, "{\"y\":-7}");
+        assert!(teardown().is_ok());
+    }
+
+    #[test]
+    fn pretty_print_file_content() {
+        let mut cfg = Config::default();
+        cfg.pretty = true;
+        let db = Store::new_with_cfg(TEST_DIR_NAME, cfg).unwrap();
+
+        #[derive(RustcEncodable)]
+        struct SubStruct {
+            c: u32,
+        };
+
+        #[derive(RustcEncodable)]
+        struct MyData {
+            a: String,
+            b: SubStruct,
+        };
+
+        let data = MyData {
+            a: "foo".to_string(),
+            b: SubStruct { c: 33 },
+        };
+
+        let id = db.save(&data).unwrap();
+        let mut f = File::open(format!("{}/{}.json", TEST_DIR_NAME, id)).unwrap();
+        let mut buffer = String::new();
+        f.read_to_string(&mut buffer).unwrap();
+        let expected = "{\n  \"a\": \"foo\",\n  \"b\": {\n    \"c\": 33\n  }\n}";
+        assert_eq!(buffer, expected);
+        assert!(teardown().is_ok());
+    }
+
+    #[test]
+    fn get() {
+        let db = Store::new(TEST_DIR_NAME).unwrap();
+        #[derive(RustcDecodable)]
+        struct MyData {
+            z: f32,
+        };
+        let mut file = File::create(format!("{}/foo.json", TEST_DIR_NAME)).unwrap();
+        Write::write_all(&mut file, "{\"z\":9.9}".as_bytes()).unwrap();
+        let obj: MyData = db.get("foo").unwrap();
+        assert_eq!(obj.z, 9.9);
+        assert!(teardown().is_ok());
+    }
+
+    #[test]
+    fn get_non_existent() {
+        let db = Store::new(TEST_DIR_NAME).unwrap();
+        let res = db.get::<X>("foobarobject");
+        assert!(res.is_err());
+        assert_eq!(res.err().unwrap().kind(), ErrorKind::NotFound);
+
+    }
+
+    #[test]
+    fn get_all() {
+        let db = Store::new(TEST_DIR_NAME).unwrap();
+        #[derive(RustcEncodable,RustcDecodable)]
+        struct X {
+            x: u32,
+            y: u32,
+        };
+
+        let mut file = File::create(format!("{}/foo.json", TEST_DIR_NAME)).unwrap();
+        Write::write_all(&mut file, "{\"x\":1, \"y\":0}".as_bytes()).unwrap();
+
+        let mut file = File::create(format!("{}/bar.json", TEST_DIR_NAME)).unwrap();
+        Write::write_all(&mut file, "{\"y\":2}".as_bytes()).unwrap();
+
+        let all_x: BTreeMap<String, X> = db.get_all().unwrap();
+        let all_y: BTreeMap<String, Y> = db.get_all().unwrap();
+        assert_eq!(all_x.get("foo").unwrap().x, 1);
+        assert!(all_x.get("bar").is_none());
+        assert_eq!(all_y.get("bar").unwrap().y, 2);
+        assert!(teardown().is_ok());
+    }
+
+    #[test]
+    fn delete() {
+        let db = Store::new(TEST_DIR_NAME).unwrap();
+        let data = Y { y: 88 };
+        let id = db.save(&data).unwrap();
+        let f_name = format!("{}/{}.json", TEST_DIR_NAME, id);
+        db.get::<Y>(&id).unwrap();
+        assert_eq!(Path::new(&f_name).exists(), true);
+        db.delete(&id).unwrap();
+        assert_eq!(Path::new(&f_name).exists(), false);
+        assert!(db.get::<Y>(&id).is_err());
+        assert!(db.delete(&id).is_err());
+        assert!(teardown().is_ok());
+    }
+
+    #[test]
+    fn delete_non_existent() {
+        let db = Store::new(TEST_DIR_NAME).unwrap();
+        let res = db.delete("blabla");
+        assert!(res.is_err());
+        assert_eq!(res.err().unwrap().kind(), ErrorKind::NotFound);
+    }
+
+    #[test]
+    fn single_save() {
+        let mut cfg = Config::default();
+        cfg.single = true;
+        let db = Store::new_with_cfg(TEST_FILE_NAME, cfg).unwrap();
+        assert_eq!(read_from_test_file(), "{}");
+        let x = X { x: 3 };
+        let y = Y { y: 4 };
+        db.save_with_id(&x, "x").unwrap();
+        db.save_with_id(&y, "y").unwrap();
+        assert_eq!(read_from_test_file(), "{\"x\":{\"x\":3},\"y\":{\"y\":4}}");
+        assert!(teardown().is_ok());
+    }
+
+    #[test]
+    fn single_save_without_file_name_ext() {
+        let mut cfg = Config::default();
+        cfg.single = true;
+        Store::new_with_cfg(TEST_DIR_NAME, cfg).unwrap();
+        assert!(Path::new(&format!("{}.json", TEST_DIR_NAME)).exists());
+        assert!(teardown().is_ok());
+    }
+
+    #[test]
+    fn single_get() {
+        let mut cfg = Config::default();
+        cfg.single = true;
+        let db = Store::new_with_cfg(TEST_FILE_NAME, cfg).unwrap();
+        write_to_test_file("{\"x\":{\"x\":8},\"y\":{\"y\":9}}");
+        let y = db.get::<Y>("y").unwrap();
+        assert_eq!(y.y, 9);
+        assert!(teardown().is_ok());
+    }
+
+    #[test]
+    fn single_get_non_existent() {
+        let mut cfg = Config::default();
+        cfg.single = true;
+        let db = Store::new_with_cfg(TEST_FILE_NAME, cfg).unwrap();
+        let res = db.get::<X>("foobarobject");
+        assert!(res.is_err());
+        assert_eq!(res.err().unwrap().kind(), ErrorKind::NotFound);
+    }
+
+    #[test]
+    fn single_get_all() {
+        let mut cfg = Config::default();
+        cfg.single = true;
+        let db = Store::new_with_cfg(TEST_FILE_NAME, cfg).unwrap();
+        write_to_test_file("{\"foo\":{\"x\":8},\"bar\":{\"x\":9}}");
+        let all: BTreeMap<String, X> = db.get_all().unwrap();
+        assert_eq!(all.get("foo").unwrap().x, 8);
+        assert_eq!(all.get("bar").unwrap().x, 9);
+        assert!(teardown().is_ok());
+    }
+
+
+    #[test]
+    fn single_delete() {
+        let mut cfg = Config::default();
+        cfg.single = true;
+        let db = Store::new_with_cfg(TEST_FILE_NAME, cfg).unwrap();
+        write_to_test_file("{\"foo\":{\"x\":8},\"bar\":{\"x\":9}}");
+        db.delete("bar").unwrap();
+        assert_eq!(read_from_test_file(), "{\"foo\":{\"x\":8}}");
+        db.delete("foo").unwrap();
+        assert_eq!(read_from_test_file(), "{}");
+        assert!(teardown().is_ok());
+    }
+
+    #[test]
+    fn single_delete_non_existent() {
+        let mut cfg = Config::default();
+        cfg.single = true;
+        let db = Store::new_with_cfg(TEST_FILE_NAME, cfg).unwrap();
+        let res = db.delete("blabla");
+        assert!(res.is_err());
+        assert_eq!(res.err().unwrap().kind(), ErrorKind::NotFound);
+    }
+
+}