diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,12 @@
+
+# Changelog
+
+All notable changes to this project will be documented in this file.
+`validators` uses [PVP versioning](https://pvp.haskell.org/).
+The CHANGELOG is available on [Github](https://github.com/luc-tielen/validators.git/CHANGELOG.md).
+
+
+## [0.0.1] - 2019-10-23
+### Added
+
+- Initial version of the library
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+The MIT License
+
+Copyright (c) 2019 Luc Tielen
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,87 @@
+
+# Validators
+
+[![CircleCI](https://circleci.com/gh/luc-tielen/validators.svg?style=svg)](https://circleci.com/gh/luc-tielen/validators)
+
+A library for validating input data in Haskell, inspired partly by [elm-validate](https://github.com/rtfeldman/elm-validate/).
+
+
+## Concepts
+
+### Validator
+
+The library provides a `Validator` datatype for checking assertions
+on input data. A validator is parametrized by 2 type variables:
+
+1. `err`: the type of error a validator can return,
+2. `subject`: the type being inspected/validated.
+
+Execute validators by using the `validate` function:
+
+```haskell
+data Error = TooSmall | TooBig
+
+>>> let validator = assert (> 10) [TooSmall]
+>>> validate validator 11  -- Success 11
+>>> validate validator 4   -- Failure [TooSmall]
+```
+
+Validators return a `Validation` type as result, which can be
+further chained or checked for the final result. This is explained in
+the [Validation](#Validation) section.
+
+Validators can also be combined using the `Semigroup` instance,
+resulting in a validator that accumulates errors from both sub-validators:
+
+```haskell
+>>> let validator = maxSize 3 [TooBig] <> minSize 2 [TooSmall]
+>>> validate validator [1, 2, 3]     -- Success [1,2,3]
+>>> validate validator [1, 2, 3, 4]  -- Failure [TooBig]
+>>> validate validator [1]           -- Failure [TooSmall]
+```
+
+The library provides helper functions for commonly used validation checks.
+See the docs for a complete overview which validators are available.
+
+
+### Validation
+
+Once a `Validator` has been executed, it will return a `Validation` data type.
+This value will contain either all accumulated errors found during the checking
+of assertions, or the given input value.
+
+Validations are composed using their `Applicative` instance. This can be useful
+for example when validating product types. Like with `Validator`, this will also
+accumulate all encountered errors.
+
+The following is an example for validating a `Person` data type:
+
+```haskell
+data PersonError = MissingName | InvalidAge
+
+type Name = String
+type Age = Int
+data Person = Person Name Age
+
+>>> let nameValidator = ifEmpty [MissingName]
+>>> let ageValidator = assert (> 0) [InvalidAge]
+>>> Person <$> validate nameValidator "" <*> validate ageValidator (subtract 1)       -- Failure [MissingName, InvalidAge]
+>>> Person <$> validate nameValidator "Alice" <*> validate ageValidator (subtract 1)  -- Failure [InvalidAge]
+>>> Person <$> validate nameValidator "Alice" <*> validate ageValidator 25            -- Success (Person "Alice" 25)
+```
+
+
+## Developing
+
+The easiest way to start developing is by using [Nix](https://nixos.org/nix/download.html).
+
+```bash
+$ git clone git@github.com:luc-tielen/validators.git
+$ cd validators
+$ nix-shell
+$ cabal new-configure  # run inside the nix-shell
+$ cabal new-build      # run inside the nix-shell
+```
+
+The most often used commands are also provided by a [Makefile](https://github.com/luc-tielen/validators/blob/master/Makefile).
+
diff --git a/lib/Data/Validation.hs b/lib/Data/Validation.hs
new file mode 100644
--- /dev/null
+++ b/lib/Data/Validation.hs
@@ -0,0 +1,92 @@
+-- | This module defines the 'Validation' data type.
+--
+-- Most functionality is the same as in the
+-- <http://hackage.haskell.org/package/validation-1.1 validation> library,
+-- but the module doesn't provide certain typeclass instances that require
+-- a dependency on the lens library.
+module Data.Validation
+  ( Validation (..),
+    toEither,
+    fromEither,
+  )
+where
+
+import Data.Bifoldable
+import Data.Bifunctor
+import Data.Bitraversable
+
+-- | A 'Validation' is very similar to 'Either' in that it contains a value of type /e/ or /a/.
+-- In contrast to Either however, Validation accumulates all errors it comes
+-- across in it's 'Applicative' instance.
+--
+-- A complete example where Validation is used can be found
+-- <https://github.com/luc-tielen/validators.git here>.
+data Validation err a
+  = Failure !err
+  | Success !a
+  deriving (Eq, Show, Ord)
+
+instance Functor (Validation err) where
+  fmap _ (Failure err) = Failure err
+  fmap f (Success a) = Success (f a)
+  {-# INLINE fmap #-}
+
+instance Bifunctor Validation where
+  bimap f _ (Failure e) = Failure (f e)
+  bimap _ g (Success a) = Success (g a)
+  {-# INLINE bimap #-}
+
+instance Foldable (Validation err) where
+  foldMap f (Success a) = f a
+  foldMap _ _ = mempty
+  {-# INLINE foldMap #-}
+
+instance Bifoldable Validation where
+  bifoldMap f _ (Failure e) = f e
+  bifoldMap _ g (Success a) = g a
+  {-# INLINE bifoldMap #-}
+
+instance Traversable (Validation err) where
+  traverse f (Success a) = Success <$> f a
+  traverse _ (Failure e) = pure (Failure e)
+  {-# INLINE traverse #-}
+
+instance Bitraversable Validation where
+  bitraverse _ g (Success a) = Success <$> g a
+  bitraverse f _ (Failure e) = Failure <$> f e
+  {-# INLINE bitraverse #-}
+
+instance Semigroup err => Applicative (Validation err) where
+
+  Failure e1 <*> Failure e2 = Failure (e1 <> e2)
+  Failure e1 <*> _ = Failure e1
+  _ <*> Failure e2 = Failure e2
+  Success f <*> Success a = Success (f a)
+  {-# INLINE (<*>) #-}
+
+  pure = Success
+  {-# INLINE pure #-}
+
+-- | Conversion function from 'Validation' to 'Either'.
+--
+-- >>> toEither (Failure 1)
+-- Left 1
+-- >>> toEither (Success True)
+-- Right True
+toEither :: Validation err a -> Either err a
+toEither v = case v of
+  Failure e -> Left e
+  Success a -> Right a
+{-# INLINE toEither #-}
+
+-- | Conversion function from 'Either' to 'Validation'.
+--
+-- >>> fromEither (Left 1)
+-- Failure 1
+-- >>> fromEither (Right True)
+-- Success True
+fromEither :: Either err a -> Validation err a
+fromEither e = case e of
+  Left a -> Failure a
+  Right b -> Success b
+{-# INLINE fromEither #-}
diff --git a/lib/Data/Validator.hs b/lib/Data/Validator.hs
new file mode 100644
--- /dev/null
+++ b/lib/Data/Validator.hs
@@ -0,0 +1,266 @@
+{-# LANGUAGE FlexibleInstances #-}
+
+-- | This module defines the 'Validator' data type and helper functions
+--   for creating various validators.
+module Data.Validator
+  ( Validator,
+    validate,
+    assert,
+    refute,
+    ifNothing,
+    ifLeft,
+    HasSize (..),
+    ifEmpty,
+    minSize,
+    maxSize,
+    IsOnlyWhiteSpace (..),
+    ifBlank,
+  )
+where
+
+import Data.Either (isRight)
+import Data.Functor.Contravariant
+import Data.Map (Map)
+import qualified Data.Map as Map
+import Data.Maybe (isJust)
+import Data.Sequence (Seq)
+import qualified Data.Sequence as Seq
+import Data.Set (Set)
+import qualified Data.Set as Set
+import qualified Data.Text as T
+import qualified Data.Text.Lazy as TL
+import Data.Validation
+
+-- | Helper data type resembling the result of a 'Validator' assertion.
+--
+-- There are only 2 possible results:
+-- 1. Ok: the validator assertion succeeded.
+-- 2. Err: the validator assertion failed.
+--
+-- Only used internally in the 'Validator' type to keep track of accumulated errors.
+data Result err = Ok | Err !err
+  deriving (Eq, Show)
+
+instance Semigroup err => Semigroup (Result err) where
+  Ok <> Ok = Ok
+  Err e1 <> Err e2 = Err $ e1 <> e2
+  Err e1 <> _ = Err e1
+  _ <> Err e2 = Err e2
+  {-# INLINE (<>) #-}
+
+instance Monoid err => Monoid (Result err) where
+  mempty = Ok
+  {-# INLINE mempty #-}
+
+-- | Datatype for checking if a validation holds for a subject.
+--
+-- - __subject__ can be any data type for which assertions need to be checked.
+-- - __err__ can be any type representing an error, but it will only be possible to
+-- combine validators if the error type has a Semigroup instance.
+--
+-- Execute a validator by passing it to the 'validate' function.
+--
+-- A Validator is both a 'Semigroup' and a 'Monoid', making it possible to combine
+-- smaller validators into larger validators. A combined validator will accumulate
+-- errors from all of it's sub-validators.
+newtype Validator err subject = Validator (subject -> Result err)
+
+instance Semigroup err => Semigroup (Validator err subject) where
+  Validator v1 <> Validator v2 = Validator $ v1 <> v2
+  {-# INLINE (<>) #-}
+
+instance Monoid err => Monoid (Validator err subject) where
+  mempty = Validator mempty
+  {-# INLINE mempty #-}
+
+instance Contravariant (Validator err) where
+  contramap f (Validator g) = Validator (g . f)
+  {-# INLINE contramap #-}
+
+-- | Runs a validator on a subject.
+--
+-- The result is a 'Validation' containing all accumulated errors,
+-- or the subject wrapped in a 'Success' value.
+validate :: Validator err subject -> subject -> Validation err subject
+validate (Validator f) a = case f a of
+  Err e -> Failure e
+  Ok -> Success a
+{-# INLINE validate #-}
+
+-- | Creates a validator that will return an error if the given predicate doesn't hold.
+--
+-- Since any predicate can be provided for checking if the subject satisfies
+-- certain conditions, this can be used to build your own custom validators.
+--
+-- Usage:
+--
+-- >>> let validator = assert (> 10) ["too small"]
+-- >>> validate validator 11
+-- Success 11
+--
+-- >>> validate validator 1
+-- Failure ["too small"]
+assert :: (subject -> Bool) -> err -> Validator err subject
+assert p err = Validator $ \subject -> if p subject then Ok else Err err
+{-# INLINE assert #-}
+
+-- | Creates a validator that will return an error if the given predicate holds.
+--
+-- Since any predicate can be provided for checking if the subject satisfies
+-- certain conditions, this can be used to build your own custom validators.
+--
+-- Usage:
+--
+-- >>> let validator = refute (> 10) ["too big"]
+-- >>> validate validator 11
+-- Failure ["too big"]
+--
+-- >>> validate validator 1
+-- Success 1
+refute :: (subject -> Bool) -> err -> Validator err subject
+refute p = assert (not . p)
+{-# INLINE refute #-}
+
+-- | Returns an error if a 'Maybe' is 'Nothing'.
+--
+-- Usage:
+--
+-- >>> let validator = ifNothing ["Found nothing."]
+-- >>> validate validator Nothing
+-- Failure ["Found nothing."]
+--
+-- >>> validate validator (Just "Bob")
+-- Success (Just "Bob")
+ifNothing :: err -> Validator err (Maybe a)
+ifNothing = assert isJust
+{-# INLINE ifNothing #-}
+
+-- | Returns an error if an 'Either' contains a 'Left'.
+--
+-- Usage:
+--
+-- >>> let validator = ifLeft ["Found left."]
+-- >>> validate validator (Left 123)
+-- Failure ["Found left."]
+--
+-- >>> validate validator (Right 456)
+-- Success (Right 456)
+ifLeft :: err -> Validator err (Either a b)
+ifLeft = assert isRight
+{-# INLINE ifLeft #-}
+
+-- | Helper typeclass for checking if a value is empty.
+class HasSize a where
+
+  size :: a -> Int
+
+  isEmpty :: a -> Bool
+  isEmpty = (== 0) . size
+
+instance HasSize [a] where
+
+  size = length
+  {-# INLINE size #-}
+
+  isEmpty = null
+  {-# INLINE isEmpty #-}
+
+instance HasSize (Map k v) where
+
+  size = Map.size
+  {-# INLINE size #-}
+
+  isEmpty = Map.null
+  {-# INLINE isEmpty #-}
+
+instance HasSize (Set a) where
+
+  size = Set.size
+  {-# INLINE size #-}
+
+  isEmpty = Set.null
+  {-# INLINE isEmpty #-}
+
+instance HasSize (Seq a) where
+
+  size = Seq.length
+  {-# INLINE size #-}
+
+  isEmpty = Seq.null
+  {-# INLINE isEmpty #-}
+
+-- | Returns an error if the function returns an "empty" value.
+--
+-- Usage:
+--
+-- >>> let validator = ifEmpty ["Empty."]
+-- >>> validate validator []
+-- Failure ["Empty."]
+--
+-- >>> validate validator [1, 2, 3]
+-- Success [1,2,3]
+-- >>> validate validator (Map.fromList [('a', 1), ('b', 2)])
+-- Success (fromList [('a',1),('b',2)])
+ifEmpty :: HasSize subject => err -> Validator err subject
+ifEmpty = refute isEmpty
+{-# INLINE ifEmpty #-}
+
+-- | Returns an error if the value has a size smaller than required.
+--
+-- Usage:
+--
+-- >>> let validator = minSize 3 ["Too small."]
+-- >>> validate validator []
+-- Failure ["Too small."]
+-- >>> validate validator [1, 2]
+-- Failure ["Too small."]
+--
+-- >>> validate validator [1, 2, 3]
+-- Success [1,2,3]
+minSize :: HasSize subject => Int -> err -> Validator err subject
+minSize x = refute ((< x) . size)
+{-# INLINE minSize #-}
+
+-- | Returns an error if the value has a size smaller than required.
+--
+-- Usage:
+--
+-- >>> let validator = maxSize 3 ["Too big."]
+-- >>> validate validator [1, 2, 3, 4]
+-- Failure ["Too big."]
+--
+-- >>> validate validator [1, 2, 3]
+-- Success [1,2,3]
+maxSize :: HasSize subject => Int -> err -> Validator err subject
+maxSize x = refute ((> x) . size)
+{-# INLINE maxSize #-}
+
+-- | Helper typeclass for checking if a value contains only whitespace characters.
+class IsOnlyWhiteSpace a where
+  isOnlyWhiteSpace :: a -> Bool
+
+instance IsOnlyWhiteSpace String where
+  isOnlyWhiteSpace = null . words
+  {-# INLINE isOnlyWhiteSpace #-}
+
+instance IsOnlyWhiteSpace TL.Text where
+  isOnlyWhiteSpace = null . TL.words
+  {-# INLINE isOnlyWhiteSpace #-}
+
+instance IsOnlyWhiteSpace T.Text where
+  isOnlyWhiteSpace = null . T.words
+  {-# INLINE isOnlyWhiteSpace #-}
+
+-- | Returns an error if the function returns a value containing only whitespace.
+--
+-- Usage:
+--
+-- >>> let validator = ifBlank ["Only whitespace."]
+-- >>> validate validator "   \t \n \r "
+-- Failure ["Only whitespace."]
+--
+-- >>> validate validator "not empty"
+-- Success "not empty"
+ifBlank :: IsOnlyWhiteSpace subject => err -> Validator err subject
+ifBlank = refute isOnlyWhiteSpace
+{-# INLINE ifBlank #-}
diff --git a/tests/Data/ValidationSpec.hs b/tests/Data/ValidationSpec.hs
new file mode 100644
--- /dev/null
+++ b/tests/Data/ValidationSpec.hs
@@ -0,0 +1,32 @@
+module Data.ValidationSpec (module Data.ValidationSpec) where
+
+import Control.Arrow ((>>>))
+import Data.Validation
+import Data.Validator
+import Test.Hspec
+
+(==>) :: (Eq a, Show a) => a -> a -> IO ()
+(==>) = shouldBe
+
+infixr 0 ==>
+
+type Name = String
+
+type Age = Int
+
+data Person = Person Name Age
+  deriving (Eq, Show)
+
+spec :: Spec
+spec = describe "Running validations" $ parallel $ do
+  it "can combine validations into bigger validations" $ do
+    let nameValidator = assert (length >>> (> 0)) ["name can't be empty"]
+        ageValidator =
+          assert (> 0) ["age must be > 0"]
+            <> assert (< 120) ["age must be < 120"]
+        runTest name age =
+          Person <$> validate nameValidator name <*> validate ageValidator age
+    runTest "" 0 ==> Failure ["name can't be empty", "age must be > 0"]
+    runTest "" 10 ==> Failure ["name can't be empty"]
+    runTest "Alice" 0 ==> Failure ["age must be > 0"]
+    runTest "Alice" 25 ==> Success $ Person "Alice" 25
diff --git a/tests/Data/ValidatorSpec.hs b/tests/Data/ValidatorSpec.hs
new file mode 100644
--- /dev/null
+++ b/tests/Data/ValidatorSpec.hs
@@ -0,0 +1,28 @@
+module Data.ValidatorSpec (module Data.ValidatorSpec) where
+
+import Data.Validation
+import Data.Validator
+import Test.Hspec
+
+(==>) :: (Eq a, Show a) => a -> a -> IO ()
+(==>) = shouldBe
+
+infixr 0 ==>
+
+data Error = TooSmall | TooBig | WrongNumber
+  deriving (Eq, Show)
+
+spec :: Spec
+spec = describe "Validators" $ parallel $ do
+  describe "Running validators" $ parallel $ do
+    it "can run single validation on a subject" $ do
+      let validator = assert (> 10) [TooSmall]
+      validate validator 11 ==> Success 11
+      validate validator 1 ==> Failure [TooSmall]
+    it "can run multiple validations on a subject" $ do
+      let validator =
+            assert (< 10) [TooBig]
+              <> assert (== 7) [WrongNumber]
+      validate validator 7 ==> Success 7
+      validate validator 6 ==> Failure [WrongNumber]
+      validate validator 11 ==> Failure [TooBig, WrongNumber]
diff --git a/tests/doctest.hs b/tests/doctest.hs
new file mode 100644
--- /dev/null
+++ b/tests/doctest.hs
@@ -0,0 +1,9 @@
+module Main (main) where
+
+import System.FilePath.Glob (glob)
+import Test.DocTest (doctest)
+
+main :: IO ()
+main = do
+  sourceFiles <- glob "lib/**/*.hs"
+  doctest sourceFiles
diff --git a/tests/test.hs b/tests/test.hs
new file mode 100644
--- /dev/null
+++ b/tests/test.hs
@@ -0,0 +1,2 @@
+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
+{-# OPTIONS_GHC -Wno-missing-export-lists #-}
diff --git a/validators.cabal b/validators.cabal
new file mode 100644
--- /dev/null
+++ b/validators.cabal
@@ -0,0 +1,77 @@
+cabal-version: 1.12
+
+-- This file has been generated from package.yaml by hpack version 0.31.2.
+--
+-- see: https://github.com/sol/hpack
+--
+-- hash: 04bfc615274aa1d27d3d495bc899783d55c2f99bf0866b6d33027777be15861e
+
+name:           validators
+version:        0.0.1
+synopsis:       Composable validations for your Haskell data types
+description:    Composable validations for your Haskell data types.
+category:       Data
+homepage:       https://github.com/luc-tielen/validators#README.md
+author:         Luc Tielen
+maintainer:     luc.tielen@gmail.com
+copyright:      2019 Luc Tielen
+license:        MIT
+license-file:   LICENSE
+build-type:     Simple
+extra-source-files:
+    README.md
+    CHANGELOG.md
+    LICENSE
+
+library
+  exposed-modules:
+      Data.Validation
+      Data.Validator
+  other-modules:
+      Paths_validators
+  hs-source-dirs:
+      lib
+  ghc-options: -Wall -Weverything -Wno-implicit-prelude -Wno-missing-import-lists -optP-Wno-nonportable-include-path -fhide-source-paths -fno-show-valid-hole-fits -fno-sort-valid-hole-fits
+  build-depends:
+      base >=4.12 && <5
+    , containers ==0.*
+    , text ==1.*
+  default-language: Haskell2010
+
+test-suite validators-doctest
+  type: exitcode-stdio-1.0
+  main-is: doctest.hs
+  other-modules:
+      Data.ValidationSpec
+      Data.ValidatorSpec
+      Paths_validators
+  hs-source-dirs:
+      tests
+  ghc-options: -Wall -Weverything -Wno-implicit-prelude -Wno-missing-import-lists -optP-Wno-nonportable-include-path -fhide-source-paths -fno-show-valid-hole-fits -fno-sort-valid-hole-fits -threaded
+  build-depends:
+      Glob >=0.10.0 && <1
+    , base >=4.12 && <5
+    , containers ==0.*
+    , doctest >=0.16.1 && <1
+    , hspec >=2.6.1 && <3.0.0
+    , text ==1.*
+    , validators
+  default-language: Haskell2010
+
+test-suite validators-test
+  type: exitcode-stdio-1.0
+  main-is: test.hs
+  other-modules:
+      Data.ValidationSpec
+      Data.ValidatorSpec
+      Paths_validators
+  hs-source-dirs:
+      tests
+  ghc-options: -Wall -Weverything -Wno-implicit-prelude -Wno-missing-import-lists -optP-Wno-nonportable-include-path -fhide-source-paths -fno-show-valid-hole-fits -fno-sort-valid-hole-fits
+  build-depends:
+      base >=4.12 && <5
+    , containers ==0.*
+    , hspec >=2.6.1 && <3.0.0
+    , text ==1.*
+    , validators
+  default-language: Haskell2010
