diff --git a/Cargo.toml b/Cargo.toml
index c0145be..828e11e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -18,7 +18,7 @@ crate-type = ["cdylib"]
 
 [dependencies]
 url = "2.5.3"
-serde = "1.0.214"
+serde = { version = "1.0.214", features = ["derive"] }
 serde_json = "1.0.104"
 # needed to enable the "js" feature for compatibility with wasm,
 # see https://docs.rs/getrandom/#webassembly-support
diff --git a/README.md b/README.md
index 394583e..70baa20 100644
--- a/README.md
+++ b/README.md
@@ -1,30 +1,82 @@
 # URL Shortener - Cloudflare Worker
 
-## Usage
+## Development
+
+### Prerequisites
+
+You have two options:
+
+Option one (recommended):
+
+- [install](https://devenv.sh/getting-started/) `devenv` && run `devenv up`
+- done.
+
+Now you can run `dev`, `fmt`, etc. (tasks are defined in [`tasks.nix`](./tasks.nix))
+
+Option two: follow Cloudflare's [guide](https://developers.cloudflare.com/workers/languages/rust/)
+
+- [install `Node.js`](https://nodejs.org/en/learn/getting-started/how-to-install-nodejs)
+- [install `wrangler`](https://bun.sh/docs/installation)
+- [install `rust`](https://www.rust-lang.org/tools/install)
 
 > [!NOTE]
-> When running locally use `http://localhost:8787`
+> the rest of the guide assumes you're using `devenv`
+>
+> if you're installing stuff manully,
+> take a look at [`tasks.nix`](./tasks.nix) for the commands
 
-### Shorten a URL
+Once you've installed the prerequisites, you can run:
+
+dev server
+
+```bash
+dev
+```
+
+rowser-based sqlite viewer
+
+```bash
+d1-viewer
+```
+
+seed local d1 database with data
 
 ```bash
-wrangler dev --config='wrangler.toml' dev --preview
+d1-seed
+```
+
+shorten a URL
+
+```bash
+curl --url http://localhost:8787/create \
+  --request 'POST' \
+  --data-binary 'https://docs.union.build/reference/graphql/?query=%7B%20__typename%20%7D'
 ```
 
+now refresh the d1 viewer page and you should see the new record
+
+## Usage
+
+> [!NOTE]
+> When running locally use `http://localhost:8787`
+
+### Shorten a URL
+
 ```bash
 curl --url http://localhost:8787/create \
   --request 'POST' \
   --data-binary 'https://docs.union.build/reference/graphql/?query=%7B%20__typename%20%7D'
 ```
 
-This will return a short id, for example:
+This will return a short the shortened URL, for example:
 
 ```sh
-7312a5
+# example
+https://localhost/26
 ```
 
 ### Expand a short URL
 
 ```bash
-curl --url http://localhost:8787/7312a5
+curl --url http://localhost:8787/26
 ```
diff --git a/flake.nix b/flake.nix
index 8d0b6c3..43732c2 100644
--- a/flake.nix
+++ b/flake.nix
@@ -1,6 +1,7 @@
 {
   description = "URL Shortener Worker";
   inputs = {
+
     nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
     systems.url = "github:nix-systems/default";
 
@@ -26,7 +27,6 @@
       packages = forEachSystem (system: {
         devenv-up = self.devShells.${system}.default.config.procfileScript;
       });
-
       devShells = forEachSystem (
         system:
         let
@@ -40,11 +40,7 @@
                 # https://devenv.sh/reference/options/
                 scripts = import ./tasks.nix;
 
-                dotenv = {
-                  enable = true;
-                  filename = [ ".env" ];
-                };
-
+                dotenv.enable = true;
                 languages.nix.enable = true;
                 languages.rust = {
                   enable = true;
@@ -62,7 +58,11 @@
 
                 # for development only
                 # this is the default location when you run d1 with `--local`
-                env.D1_DATABASE_FILEPATH = ".wrangler/state/v3/d1/miniflare-D1DatabaseObject/*.db";
+                env.D1_DATABASE_FILEPATH =
+                  let
+                    dbDir = ".wrangler/state/v3/d1/miniflare-D1DatabaseObject";
+                  in
+                  "${dbDir}/$(${pkgs.findutils}/bin/find ${dbDir} -maxdepth 1 -name '*.sqlite' ! -name '*-shm' ! -name '*-wal' -printf '%f\n' | head -n1)";
 
                 packages = with pkgs; [
                   jq
@@ -70,6 +70,8 @@
                   bun
                   taplo
                   direnv
+                  sqlite
+                  deadnix
                   sqlfluff
                   binaryen
                   nixfmt-rfc-style
diff --git a/src/lib.rs b/src/lib.rs
index 4f43f6b..62d9f06 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -9,31 +9,56 @@ struct GenericResponse {
     message: String,
 }
 
+const DEV_ROUTES: [&str; 2] = ["/list", "/env"];
+
+pub fn get_secret(name: &str, env: &Env) -> Option<String> {
+    match env.secret(name) {
+        Ok(value) => Some(value.to_string()),
+        Err(_) => None,
+    }
+}
+
+pub fn get_var(name: &str, env: &Env) -> Option<String> {
+    match env.var(name) {
+        Ok(value) => Some(value.to_string()),
+        Err(_) => None,
+    }
+}
+
 #[event(fetch)]
-async fn main(request: Request, env: Env, _context: Context) -> Result<Response> {
-    let environment = env.var("ENVIRONMENT").unwrap().to_string();
+async fn fetch(request: Request, env: Env, _context: Context) -> Result<Response> {
+    let environment = get_var("ENVIRONMENT", &env).unwrap_or_default();
     if environment.trim().is_empty() {
         return Response::error("not allowed", 403);
     }
 
-    let mut router = Router::new()
-        // public routes
-        .get("/", index_route)
-        .post("/", index_route)
+    let router = Router::new()
+        .get("/", |_, _| Response::ok("zkgm"))
+        .post("/", |_, _| Response::ok("zkgm"))
         .post_async("/create", handle_create)
         .get_async("/:key", handle_url_expand);
 
-    if environment == "development" {
-        // dev-only routes
-        // quick way to check records are inserted
-        router = router.get_async("/list", dev_handle_list_urls);
+    let url = request.url()?;
+    if !DEV_ROUTES.contains(&url.path()) {
+        return router.run(request, env).await;
     }
 
-    return router.run(request, env).await;
-}
+    console_log!("{}", url.query().unwrap_or_default());
 
-pub fn index_route(_request: Request, _context: RouteContext<()>) -> worker::Result<Response> {
-    Response::ok("zkgm")
+    let url_key = url.query().and_then(|q| q.split("key=").nth(1));
+    if url_key.is_none() {
+        return router.run(request, env).await;
+    }
+
+    let stored_key = get_secret("DEV_ROUTES_KEY", &env).unwrap_or_default();
+
+    if url_key != Some(&stored_key) {
+        return router.run(request, env).await;
+    }
+    return router
+        .get_async("/list", dev_handle_list_urls)
+        .run(request, env)
+        .await;
 }
 
 // handles `POST /create --data-binary 'https://example.com/foo/bar'`
@@ -41,20 +66,33 @@ pub async fn handle_create(
     mut request: Request,
     context: RouteContext<()>,
 ) -> worker::Result<Response> {
-    let url = request.text().await?;
-    if Url::parse(&url).is_err() {
+    let payload_url = request.text().await?;
+    if Url::parse(&payload_url).is_err() {
         return Response::error("provided url is not valid", 400);
     }
 
-    let d1 = context.env.d1("DB");
-    let statement = d1?.prepare("INSERT INTO urls (url) VALUES (?)");
-    let query = statement.bind(&[url.into()]);
-    let result = query?.run().await?.success();
+    let d1 = context.env.d1("DB")?;
+    let statement = d1.prepare("INSERT INTO urls (url) VALUES (?)");
+    let query = statement.bind(&[payload_url.into()]);
+    let result = query?.run().await?;
 
-    if result {
-        return Response::ok("ok");
+    if result.error().is_some() {
+        return Response::error("failed to insert new key", 500);
     }
 
+    let query_statement = d1.prepare("SELECT id FROM urls ORDER BY id DESC LIMIT 1");
+    let query = query_statement.bind(&[]);
+    let result = query?.first::<Value>(None).await?.unwrap();
+
+    if let Value::Object(object) = result {
+        if let Some(Value::Number(id)) = object.get("id") {
+            return Response::ok(format!(
+                "https://{}/{}",
+                request.url().unwrap().host_str().unwrap(),
+                id
+            ));
+        }
+    }
     Response::error("failed to insert new key", 500)
 }
 
@@ -63,8 +101,10 @@ pub async fn handle_url_expand(
     request: Request,
     context: RouteContext<()>,
 ) -> worker::Result<Response> {
-    let key = &request.path().to_string()[1..];
-    if key.parse::<u64>().is_err() {
+    let url = request.url()?;
+    let key = url.path().trim_start_matches('/');
+
+    if key.parse::<u64>().is_err() || key.is_empty() {
         return Response::error("invalid key: ".to_string() + key, 400);
     }
 
@@ -84,6 +124,7 @@ pub async fn handle_url_expand(
     }
 }
 
+// dev-only route: quick way to check records are inserted
 pub async fn dev_handle_list_urls(
     _request: Request,
     context: RouteContext<()>,
diff --git a/tasks.nix b/tasks.nix
index 061f6dd..fce8bde 100644
--- a/tasks.nix
+++ b/tasks.nix
@@ -1,4 +1,7 @@
 {
+  echo-env.exec = ''
+    echo $D1_DATABASE_FILEPATH
+  '';
   wrangler.exec = ''
     bunx wrangler@latest --config='wrangler.toml' "$@"
   '';
@@ -12,6 +15,7 @@
     taplo lint *.toml
     cargo clippy --all-targets --all-features
     sqlfluff lint --dialect sqlite ./schema.sql
+    deadnix --no-lambda-pattern-names && statix check .
   '';
   build.exec = ''
     cargo build --release --target wasm32-unknown-unknown
@@ -20,9 +24,12 @@
   dev.exec = ''
     bunx wrangler@latest --config='wrangler.toml' dev "$@"
   '';
+  d1-create-database.exec = ''
+    bunx wrangler@latest --config='wrangler.toml' d1 create url-short-d1 "$@"
+  '';
   # optional: `--local`, `--remote`
   d1-bootstrap.exec = ''
-    bunx wrangler@latest --config='wrangler.toml' d1 execute url-short-d1 --file='schema.sql' "$@"
+    bunx wrangler@latest --config='wrangler.toml' d1 execute url-short-d1 --file='schema.sql'
   '';
   # optional: `--local`, `--remote`
   # required: `--command="SELECT * FROM urls"`
@@ -34,7 +41,7 @@
   '';
   # only works locally in development
   d1-viewer.exec = ''
-    bunx @outerbase/studio@latest $D1_DATABASE_FILEPATH --port=4000
+    bunx @outerbase/studio@latest $(eval echo $D1_DATABASE_FILEPATH) --port=4000
   '';
   deploy.exec = ''
     bunx wrangler@latest deploy --env='production' --config='wrangler.toml'