diff --git a/example/.swerve/config.yml b/example/.swerve/config.yml
index b6f87d200c03b3ce036855605d449365f9e97853..2b85e70b1569b76425d6eedc510310a3ec97d8fa 100644
--- a/example/.swerve/config.yml
+++ b/example/.swerve/config.yml
@@ -4,7 +4,7 @@ server:
   port: 9000
 routes:
   - route: /users/@user_id
-    script: scripts/get_user_by_id.dyon
+    script: get_user.lua
   - route: /users
     response:
       failure_rate: 5
diff --git a/example/.swerve/get_user.lua b/example/.swerve/get_user.lua
index c83badd72efd31a65beff3951955a0a487427995..fd91f9f9c36eea4a447524d7ebc4425e737c6fef 100644
--- a/example/.swerve/get_user.lua
+++ b/example/.swerve/get_user.lua
@@ -1 +1,12 @@
-print(1, 2)
\ No newline at end of file
+print("We're going to the races");
+
+foo = 1 + 1;
+
+r = empty_response();
+r:set_status(404);
+-- r:set_header("foo", "bar");
+-- r:set_body()
+
+return response(200, "application/json", json_encode({ foo = 123 }));
+
+-- return response(200, "application/json", '{ "foo": ' .. foo .. ' }')
\ No newline at end of file
diff --git a/src/routing/request_rewriter.rs b/src/routing/request_rewriter.rs
index eaa43706e9ea37a49c1e97208d3f5acd403ca5b7..d39e6381d8afe2ccabfb70b02756d0eefd75d806 100644
--- a/src/routing/request_rewriter.rs
+++ b/src/routing/request_rewriter.rs
@@ -1,5 +1,5 @@
 use rocket::fairing::{Fairing, Info, Kind};
-use rocket::{Request, Data, Rocket, State};
+use rocket::{Request, Data, State};
 use cli::SwerveConfig;
 use std::collections::HashMap;
 use routing::request::path::MatchablePath;
diff --git a/src/routing/scripting.rs b/src/routing/scripting.rs
index 460e4531453d550d6a8f029a2d514c63ca632c6a..c7a34582311e6d5f6847fea7ecdec634e7c8b31f 100644
--- a/src/routing/scripting.rs
+++ b/src/routing/scripting.rs
@@ -1,7 +1,7 @@
 use routing::request;
 use server::LuaRuntime;
 use rlua::{Lua};
-use scripting::run_script;
+use scripting::{run_script, ScriptResponse};
 
 use rocket::request::{FromForm, FormItems};
 use std::collections::HashMap;
@@ -35,12 +35,7 @@ impl <'form> FromForm<'form> for ScriptParams {
 
 
 #[get("/__run_script__?<params>")]
-pub fn route_script(params: ScriptParams, runtime: LuaRuntime) -> String {
+pub fn route_script(params: ScriptParams, runtime: LuaRuntime) -> ScriptResponse {
 	let lua: Lua = runtime.into(); //todo: Use This
-
-	println!("{:?}", params);
-
-	run_script("example/.swerve/get_user.lua", &lua);
-
-	String::from("Yes")
+	run_script(format!("example/.swerve/{}", params.script_name), &lua).unwrap_or_else(|| ScriptResponse::default())
 }
\ No newline at end of file
diff --git a/src/scripting/mod.rs b/src/scripting/mod.rs
index e7d98761b4f3f37a01d3f416fd8b941ec218de42..2d1e71875a98d3c26b96a8a80143ed8544d7f9e6 100644
--- a/src/scripting/mod.rs
+++ b/src/scripting/mod.rs
@@ -1,3 +1,7 @@
+pub mod script_request;
+
 mod run_script;
+mod script_response;
 
-pub use self::run_script::run_script;
\ No newline at end of file
+pub use self::run_script::run_script;
+pub use self::script_response::ScriptResponse;
\ No newline at end of file
diff --git a/src/scripting/run_script.rs b/src/scripting/run_script.rs
index 85d927a54949b46fec3e2aa80acee2f637714f09..8542d359f7a03b1fcb1f247e7c6d43ba15edfa76 100644
--- a/src/scripting/run_script.rs
+++ b/src/scripting/run_script.rs
@@ -2,9 +2,10 @@ use std::convert::AsRef;
 use std::path::Path;
 use std::fs::File;
 use std::io::Read;
-use rlua::Lua;
+use rlua::{Lua, UserData};
+use scripting::ScriptResponse;
 
-pub fn run_script<P: AsRef<Path>>(path: P, mut lua: &Lua) -> Option<String> {
+pub fn run_script<P: AsRef<Path>>(path: P, mut lua: &Lua) -> Option<ScriptResponse> {
     let mut file = File::open(&path).unwrap();
     let mut buf = String::new();
 
@@ -13,9 +14,9 @@ pub fn run_script<P: AsRef<Path>>(path: P, mut lua: &Lua) -> Option<String> {
 		Err(_) => return None,
 	}
 
-	println!("{}", buf);
+	let file_name = path.as_ref()
+		.file_name()
+		.and_then(|name| name.to_str());
 
-	lua.eval::<()>(&buf, None);
-
-    Some(buf)
+	lua.eval::<ScriptResponse>(&buf, file_name).map_err(|e| println!("{}", e)).ok()
 }
\ No newline at end of file
diff --git a/src/scripting/script_request.rs b/src/scripting/script_request.rs
new file mode 100644
index 0000000000000000000000000000000000000000..c001e69673def69cd68772b539aed2a347507995
--- /dev/null
+++ b/src/scripting/script_request.rs
@@ -0,0 +1,29 @@
+use rocket::request::{FromForm, FormItems};
+use std::collections::HashMap;
+
+#[derive(Debug)]
+pub struct ScriptParams {
+	pub script_name: String,
+	pub script_params: HashMap<String, String>,
+}
+
+impl <'form> FromForm<'form> for ScriptParams {
+	type Error = ();
+
+	fn from_form(items: &mut FormItems<'form>, _: bool) -> Result<ScriptParams, Self::Error> {
+		let mut script_name: Option<String> = None;
+		let mut script_params: HashMap<String, String> = HashMap::new();
+
+		for (key, value) in items {
+			match key.as_str() {
+				"script_path" if script_name.is_none() => { script_name = Some(String::from(value.as_str())); },
+				_ => { script_params.insert(String::from(key.as_str()), String::from(value.as_str())); },
+			};
+		}
+
+		match script_name {
+			Some(name) => Ok(ScriptParams { script_name: name, script_params }),
+			None => Err(()),
+		}
+	}
+}
\ No newline at end of file
diff --git a/src/scripting/script_response.rs b/src/scripting/script_response.rs
new file mode 100644
index 0000000000000000000000000000000000000000..91023018a370cd32ec1492bf9b547259d72cb2c7
--- /dev/null
+++ b/src/scripting/script_response.rs
@@ -0,0 +1,46 @@
+use rlua::{UserData, UserDataMethods, Table};
+use rocket::Request;
+use rocket::http::Status;
+use rocket::response::{Response, Responder, ResponseBuilder};
+use std::default::Default;
+use std::io::Cursor;
+
+#[derive(Debug, Clone)]
+pub struct ScriptResponse {
+	pub status: u16,
+	pub content_type: String,
+	pub body: Option<String>,
+}
+
+impl Default for ScriptResponse {
+	fn default() -> Self {
+		ScriptResponse {
+			status: 500,
+			content_type: "text/plain".into(),
+			body: Some("Failed to return resposne from script".into()),
+		}
+	}
+}
+
+
+
+impl UserData for ScriptResponse {
+	fn add_methods(methods: &mut UserDataMethods<Self>) {
+		methods.add_method_mut("set_status", |_, response: &mut ScriptResponse, status: u16| {
+			response.status = status;
+			Ok(())
+		})
+	}
+}
+
+impl <'r>Responder<'r> for ScriptResponse {
+	fn respond_to(self, _: &Request) -> Result<Response<'r>, Status> {
+		let mut r = Response::build();
+		r.status(Status::raw(self.status));
+		r.raw_header("Content-Type", self.content_type);
+		if let Some(body) = self.body {
+			r.sized_body(Cursor::new(body));
+		}
+		r.ok()
+	}
+}
\ No newline at end of file
diff --git a/src/scripts/json.lua b/src/scripts/json.lua
new file mode 100644
index 0000000000000000000000000000000000000000..617bd18e30c111af59025742d6d34e3bd058d404
--- /dev/null
+++ b/src/scripts/json.lua
@@ -0,0 +1,404 @@
+--
+-- json.lua
+--
+-- Copyright (c) 2018 rxi
+--
+-- 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.
+--
+
+-- Source: https://github.com/rxi/json.lua
+
+-- Changes:
+-- This library has been modified so that, when loaded into Swerve, it will expose
+-- json_encode and json_decode globals, instead of being imported from a file as a
+-- single 'json' object
+
+local json = { _version = "0.1.1" }
+
+-------------------------------------------------------------------------------
+-- Encode
+-------------------------------------------------------------------------------
+
+local encode
+
+local escape_char_map = {
+  [ "\\" ] = "\\\\",
+  [ "\"" ] = "\\\"",
+  [ "\b" ] = "\\b",
+  [ "\f" ] = "\\f",
+  [ "\n" ] = "\\n",
+  [ "\r" ] = "\\r",
+  [ "\t" ] = "\\t",
+}
+
+local escape_char_map_inv = { [ "\\/" ] = "/" }
+for k, v in pairs(escape_char_map) do
+  escape_char_map_inv[v] = k
+end
+
+
+local function escape_char(c)
+  return escape_char_map[c] or string.format("\\u%04x", c:byte())
+end
+
+
+local function encode_nil(val)
+  return "null"
+end
+
+
+local function encode_table(val, stack)
+  local res = {}
+  stack = stack or {}
+
+  -- Circular reference?
+  if stack[val] then error("circular reference") end
+
+  stack[val] = true
+
+  if val[1] ~= nil or next(val) == nil then
+    -- Treat as array -- check keys are valid and it is not sparse
+    local n = 0
+    for k in pairs(val) do
+      if type(k) ~= "number" then
+        error("invalid table: mixed or invalid key types")
+      end
+      n = n + 1
+    end
+    if n ~= #val then
+      error("invalid table: sparse array")
+    end
+    -- Encode
+    for i, v in ipairs(val) do
+      table.insert(res, encode(v, stack))
+    end
+    stack[val] = nil
+    return "[" .. table.concat(res, ",") .. "]"
+
+  else
+    -- Treat as an object
+    for k, v in pairs(val) do
+      if type(k) ~= "string" then
+        error("invalid table: mixed or invalid key types")
+      end
+      table.insert(res, encode(k, stack) .. ":" .. encode(v, stack))
+    end
+    stack[val] = nil
+    return "{" .. table.concat(res, ",") .. "}"
+  end
+end
+
+
+local function encode_string(val)
+  return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"'
+end
+
+
+local function encode_number(val)
+  -- Check for NaN, -inf and inf
+  if val ~= val or val <= -math.huge or val >= math.huge then
+    error("unexpected number value '" .. tostring(val) .. "'")
+  end
+  return string.format("%.14g", val)
+end
+
+
+local type_func_map = {
+  [ "nil"     ] = encode_nil,
+  [ "table"   ] = encode_table,
+  [ "string"  ] = encode_string,
+  [ "number"  ] = encode_number,
+  [ "boolean" ] = tostring,
+}
+
+
+encode = function(val, stack)
+  local t = type(val)
+  local f = type_func_map[t]
+  if f then
+    return f(val, stack)
+  end
+  error("unexpected type '" .. t .. "'")
+end
+
+
+function json_encode(val)
+  return ( encode(val) )
+end
+
+
+-------------------------------------------------------------------------------
+-- Decode
+-------------------------------------------------------------------------------
+
+local parse
+
+local function create_set(...)
+  local res = {}
+  for i = 1, select("#", ...) do
+    res[ select(i, ...) ] = true
+  end
+  return res
+end
+
+local space_chars   = create_set(" ", "\t", "\r", "\n")
+local delim_chars   = create_set(" ", "\t", "\r", "\n", "]", "}", ",")
+local escape_chars  = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u")
+local literals      = create_set("true", "false", "null")
+
+local literal_map = {
+  [ "true"  ] = true,
+  [ "false" ] = false,
+  [ "null"  ] = nil,
+}
+
+
+local function next_char(str, idx, set, negate)
+  for i = idx, #str do
+    if set[str:sub(i, i)] ~= negate then
+      return i
+    end
+  end
+  return #str + 1
+end
+
+
+local function decode_error(str, idx, msg)
+  local line_count = 1
+  local col_count = 1
+  for i = 1, idx - 1 do
+    col_count = col_count + 1
+    if str:sub(i, i) == "\n" then
+      line_count = line_count + 1
+      col_count = 1
+    end
+  end
+  error( string.format("%s at line %d col %d", msg, line_count, col_count) )
+end
+
+
+local function codepoint_to_utf8(n)
+  -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa
+  local f = math.floor
+  if n <= 0x7f then
+    return string.char(n)
+  elseif n <= 0x7ff then
+    return string.char(f(n / 64) + 192, n % 64 + 128)
+  elseif n <= 0xffff then
+    return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128)
+  elseif n <= 0x10ffff then
+    return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128,
+                       f(n % 4096 / 64) + 128, n % 64 + 128)
+  end
+  error( string.format("invalid unicode codepoint '%x'", n) )
+end
+
+
+local function parse_unicode_escape(s)
+  local n1 = tonumber( s:sub(3, 6),  16 )
+  local n2 = tonumber( s:sub(9, 12), 16 )
+  -- Surrogate pair?
+  if n2 then
+    return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000)
+  else
+    return codepoint_to_utf8(n1)
+  end
+end
+
+
+local function parse_string(str, i)
+  local has_unicode_escape = false
+  local has_surrogate_escape = false
+  local has_escape = false
+  local last
+  for j = i + 1, #str do
+    local x = str:byte(j)
+
+    if x < 32 then
+      decode_error(str, j, "control character in string")
+    end
+
+    if last == 92 then -- "\\" (escape char)
+      if x == 117 then -- "u" (unicode escape sequence)
+        local hex = str:sub(j + 1, j + 5)
+        if not hex:find("%x%x%x%x") then
+          decode_error(str, j, "invalid unicode escape in string")
+        end
+        if hex:find("^[dD][89aAbB]") then
+          has_surrogate_escape = true
+        else
+          has_unicode_escape = true
+        end
+      else
+        local c = string.char(x)
+        if not escape_chars[c] then
+          decode_error(str, j, "invalid escape char '" .. c .. "' in string")
+        end
+        has_escape = true
+      end
+      last = nil
+
+    elseif x == 34 then -- '"' (end of string)
+      local s = str:sub(i + 1, j - 1)
+      if has_surrogate_escape then
+        s = s:gsub("\\u[dD][89aAbB]..\\u....", parse_unicode_escape)
+      end
+      if has_unicode_escape then
+        s = s:gsub("\\u....", parse_unicode_escape)
+      end
+      if has_escape then
+        s = s:gsub("\\.", escape_char_map_inv)
+      end
+      return s, j + 1
+
+    else
+      last = x
+    end
+  end
+  decode_error(str, i, "expected closing quote for string")
+end
+
+
+local function parse_number(str, i)
+  local x = next_char(str, i, delim_chars)
+  local s = str:sub(i, x - 1)
+  local n = tonumber(s)
+  if not n then
+    decode_error(str, i, "invalid number '" .. s .. "'")
+  end
+  return n, x
+end
+
+
+local function parse_literal(str, i)
+  local x = next_char(str, i, delim_chars)
+  local word = str:sub(i, x - 1)
+  if not literals[word] then
+    decode_error(str, i, "invalid literal '" .. word .. "'")
+  end
+  return literal_map[word], x
+end
+
+
+local function parse_array(str, i)
+  local res = {}
+  local n = 1
+  i = i + 1
+  while 1 do
+    local x
+    i = next_char(str, i, space_chars, true)
+    -- Empty / end of array?
+    if str:sub(i, i) == "]" then
+      i = i + 1
+      break
+    end
+    -- Read token
+    x, i = parse(str, i)
+    res[n] = x
+    n = n + 1
+    -- Next token
+    i = next_char(str, i, space_chars, true)
+    local chr = str:sub(i, i)
+    i = i + 1
+    if chr == "]" then break end
+    if chr ~= "," then decode_error(str, i, "expected ']' or ','") end
+  end
+  return res, i
+end
+
+
+local function parse_object(str, i)
+  local res = {}
+  i = i + 1
+  while 1 do
+    local key, val
+    i = next_char(str, i, space_chars, true)
+    -- Empty / end of object?
+    if str:sub(i, i) == "}" then
+      i = i + 1
+      break
+    end
+    -- Read key
+    if str:sub(i, i) ~= '"' then
+      decode_error(str, i, "expected string for key")
+    end
+    key, i = parse(str, i)
+    -- Read ':' delimiter
+    i = next_char(str, i, space_chars, true)
+    if str:sub(i, i) ~= ":" then
+      decode_error(str, i, "expected ':' after key")
+    end
+    i = next_char(str, i + 1, space_chars, true)
+    -- Read value
+    val, i = parse(str, i)
+    -- Set
+    res[key] = val
+    -- Next token
+    i = next_char(str, i, space_chars, true)
+    local chr = str:sub(i, i)
+    i = i + 1
+    if chr == "}" then break end
+    if chr ~= "," then decode_error(str, i, "expected '}' or ','") end
+  end
+  return res, i
+end
+
+
+local char_func_map = {
+  [ '"' ] = parse_string,
+  [ "0" ] = parse_number,
+  [ "1" ] = parse_number,
+  [ "2" ] = parse_number,
+  [ "3" ] = parse_number,
+  [ "4" ] = parse_number,
+  [ "5" ] = parse_number,
+  [ "6" ] = parse_number,
+  [ "7" ] = parse_number,
+  [ "8" ] = parse_number,
+  [ "9" ] = parse_number,
+  [ "-" ] = parse_number,
+  [ "t" ] = parse_literal,
+  [ "f" ] = parse_literal,
+  [ "n" ] = parse_literal,
+  [ "[" ] = parse_array,
+  [ "{" ] = parse_object,
+}
+
+
+parse = function(str, idx)
+  local chr = str:sub(idx, idx)
+  local f = char_func_map[chr]
+  if f then
+    return f(str, idx)
+  end
+  decode_error(str, idx, "unexpected character '" .. chr .. "'")
+end
+
+
+function json_decode(str)
+  if type(str) ~= "string" then
+    error("expected argument of type string, got " .. type(str))
+  end
+  local res, idx = parse(str, next_char(str, 1, space_chars, true))
+  idx = next_char(str, idx, space_chars, true)
+  if idx <= #str then
+    decode_error(str, idx, "trailing garbage")
+  end
+  return res
+end
\ No newline at end of file
diff --git a/src/server/lua.rs b/src/server/lua.rs
index 3936eeedf3ee7f8f8ee339ebb7b3e74073af34d4..048ab172ea44737025614a44816f4f516bba778b 100644
--- a/src/server/lua.rs
+++ b/src/server/lua.rs
@@ -1,7 +1,11 @@
-use rlua::{Lua};
+use rlua::{Lua, Result as LuaResult, FromLua};
 use rocket::{Outcome, http};
 use rocket::request::{FromRequest, Request};
 use std::convert::{Into, AsRef, AsMut};
+use scripting;
+use serde::Serialize;
+
+const LIB_JSON_ENCODE: &'static str = include_str!("../scripts/json.lua");
 
 pub struct LuaRuntime(Lua);
 impl Into<Lua> for LuaRuntime {
@@ -24,18 +28,45 @@ impl <'a, 'req>FromRequest<'a, 'req> for LuaRuntime {
     type Error = ();
 
     fn from_request(_request: &'a Request<'req>) -> Outcome<Self, (http::Status, ()), ()> {
-        Outcome::Success(create_runtime(false))
+        match create_runtime(false) {
+			Ok(runtime) => Outcome::Success(runtime),
+			_ => Outcome::Failure((http::Status::raw(500), ())),
+		}
     }
 }
 
-pub fn create_runtime(with_debug: bool) -> LuaRuntime {
+pub fn create_runtime(with_debug: bool) -> LuaResult<LuaRuntime> {
     let runtime = if with_debug {
         unsafe { Lua::new_with_debug() }
     } else {
         Lua::new()
     };
 
-    // Customise runtime here
+	{
+		runtime.eval::<()>(LIB_JSON_ENCODE, Some("json.lua".into()))?
+	}
+
+	{
+		let globals = &runtime.globals();
+		let response_constructor = runtime.create_function(|_, (status, content_type, body): (u16, String, String)| {
+			Ok(scripting::ScriptResponse {
+				status,
+				content_type,
+				body: Some(body),
+			})
+		})?;
+
+		let empty_response_constructor = runtime.create_function(|_, (): ()| {
+			Ok(scripting::ScriptResponse {
+				status: 204,
+				content_type: "text/plain".into(),
+				body: None,
+			})
+		})?;
+
+		globals.set("response", response_constructor)?;
+		globals.set("empty_response", empty_response_constructor)?;
+	}
 
-    LuaRuntime(runtime)
+    Ok(LuaRuntime(runtime))
 }
\ No newline at end of file