diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..6b09517
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,52 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/.pnp
+.pnp.js
+
+# testing
+/coverage
+
+# next.js
+/.next/
+/out/
+
+# production
+/build
+
+# misc
+.DS_Store
+*.pem
+
+# debug
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# local env files
+_.env
+.env*.local
+.env.dev
+.env
+.env.production
+.env.staging
+.env.development
+.env.master
+key.json
+key.txt
+sec-push.sh
+
+# vercel
+.vercel
+
+# typescript
+*.tsbuildinfo
+next-env.d.ts
+
+# other branches
+/dev-branch
+
+# other
+output.txt
+test.js
\ No newline at end of file
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 0000000..6aa16d1
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,7 @@
+{
+    "trailingComma": "es5",
+    "tabWidth": 4,
+    "semi": true,
+    "singleQuote": false,
+    "endOfLine": "lf"
+}
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..e6240fb
--- /dev/null
+++ b/README.md
@@ -0,0 +1,15 @@
+# symposia-video
+
+To install dependencies:
+
+```bash
+bun install
+```
+
+To run:
+
+```bash
+bun run index.js
+```
+
+This project was created using `bun init` in bun v1.0.30. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime.
diff --git a/bun.lockb b/bun.lockb
new file mode 100755
index 0000000..a73a26b
Binary files /dev/null and b/bun.lockb differ
diff --git a/cspell.json b/cspell.json
new file mode 100644
index 0000000..e7eb5e1
--- /dev/null
+++ b/cspell.json
@@ -0,0 +1,12 @@
+{
+    "version": "0.2",
+    "ignorePaths": [],
+    "dictionaryDefinitions": [],
+    "dictionaries": [],
+    "words": [
+        "Filip",
+        "Niklas"
+    ],
+    "ignoreWords": [],
+    "import": []
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..e5776cf
--- /dev/null
+++ b/package.json
@@ -0,0 +1,25 @@
+{
+  "name": "caravan",
+  "version": "0.0.1",
+  "description": "storage server for symposia",
+  "scripts": {
+    "start": "bun run src/index.ts",
+    "dev": "bun --watch src/index.ts"
+  },
+  "author": "Filip Niklas",
+  "license": "MIT",
+  "dependencies": {
+    "@google-cloud/storage": "^7.11.0",
+    "@types/express": "^4.17.21",
+    "@types/node": "^20.12.12",
+    "dotenv": "^16.4.5",
+    "express": "^4.19.2",
+    "typescript": "^5.4.5"
+  },
+  "devDependencies": {
+    "@types/bun": "latest"
+  },
+  "peerDependencies": {
+    "typescript": "^5.0.0"
+  }
+}
\ No newline at end of file
diff --git a/src/bucket.ts b/src/bucket.ts
new file mode 100644
index 0000000..dcbe8f8
--- /dev/null
+++ b/src/bucket.ts
@@ -0,0 +1,38 @@
+import { Storage, type GetSignedUrlConfig } from "@google-cloud/storage";
+
+const storage = new Storage();
+
+const PRIMARY_BUCKET_NAME = process.env.GCP_PRIMARY_BUCKET_NAME ?? "invalid";
+const SECONDARY_BUCKET_NAME =
+    process.env.GCP_SECONDARY_BUCKET_NAME ?? "invalid";
+
+/**
+ * Storage bucket reference instance. Call methods on this object to interact with the bucket.
+ * @see https://cloud.google.com/nodejs/docs/reference/storage/latest
+ */
+export const primaryBucket = storage.bucket(PRIMARY_BUCKET_NAME);
+export const secondaryBucket = storage.bucket(SECONDARY_BUCKET_NAME);
+
+// export async function TestBucket() {
+//     const sa = await storage.getProjectId();
+//     console.log(sa);
+//     const a = await storage.getServiceAccount();
+//     console.log(a);
+// }
+
+export async function gcGenerateReadSignedUrl({
+    fileName,
+    id,
+}: {
+    fileName: string;
+    id: string;
+}) {
+    const filePath = `video/${id}/${fileName}`;
+    const options: GetSignedUrlConfig = {
+        version: "v4",
+        action: "read",
+        expires: Date.now() + 15 * 60 * 1000, // 15 minutes
+    };
+    const [url] = await primaryBucket.file(filePath).getSignedUrl(options);
+    return url;
+}
diff --git a/src/index.ts b/src/index.ts
new file mode 100644
index 0000000..b53376d
--- /dev/null
+++ b/src/index.ts
@@ -0,0 +1,28 @@
+import express, { type Express, type Request, type Response } from "express";
+import dotenv from "dotenv";
+import { gcGenerateReadSignedUrl } from "./bucket";
+
+dotenv.config();
+
+const app: Express = express();
+const port = process.env.PORT || 3000;
+
+app.get("/", async (req: Request, res: Response) => {
+    const fileName = "VID_20200103_135115.mp4";
+    const id = "cluvqhyly0007uwfdmg2hn33a";
+    const url = await gcGenerateReadSignedUrl({ fileName, id });
+    res.send(url);
+});
+
+app.get("/:id/:filename", async (req: Request, res: Response) => {
+    const fileName = req.params.filename;
+    const id = req.params.id;
+    const url = await gcGenerateReadSignedUrl({ fileName, id });
+    res.send(url);
+});
+
+app.listen(port, () => {
+    console.log(`Server listening on port ${port}`);
+});
+
+// http://localhost:3000/cluvqhyly0007uwfdmg2hn33a/VID_20200103_135115.mp4
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..0fef23a
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,27 @@
+{
+  "compilerOptions": {
+    // Enable latest features
+    "lib": ["ESNext"],
+    "target": "ESNext",
+    "module": "ESNext",
+    "moduleDetection": "force",
+    "jsx": "react-jsx",
+    "allowJs": true,
+
+    // Bundler mode
+    "moduleResolution": "bundler",
+    "allowImportingTsExtensions": true,
+    "verbatimModuleSyntax": true,
+    "noEmit": true,
+
+    // Best practices
+    "strict": true,
+    "skipLibCheck": true,
+    "noFallthroughCasesInSwitch": true,
+
+    // Some stricter flags (disabled by default)
+    "noUnusedLocals": false,
+    "noUnusedParameters": false,
+    "noPropertyAccessFromIndexSignature": false
+  }
+}