Custom ReasonML Server (#6970)

- A typesafe custom server built in reasonml.
This commit is contained in:
Tev 2019-04-09 22:15:41 +03:00 committed by Joe Haddad
parent ca565fc6e4
commit 9c8d7290df
9 changed files with 215 additions and 0 deletions

View file

@ -0,0 +1,26 @@
*.exe
*.obj
*.out
*.compile
*.native
*.byte
*.cmo
*.annot
*.cmi
*.cmx
*.cmt
*.cmti
*.cma
*.a
*.cmxa
*.obj
*~
*.annot
*.cmj
*.bak
lib/bs
*.mlast
*.mliast
.vscode
.merlin
.bsb.lock

View file

@ -0,0 +1,30 @@
# Custom server REASONML
# Install it and run:
```bash
npm install
npm run dev
# or
yarn
yarn dev
```
# Build the app
```bash
yarn next:build
npm run next:build
```
# Run the production app
Run this command after yarn build.
```bash
yarn start
```
# The idea behind this example
ReasonML is an exciting new language and since it can compile directly to JS via bucklescript
that means that we can power our backend server with REASONML and also have the frontend built with
reasonreact, which is covered in another [example](https://github.com/zeit/next.js/tree/canary/examples/with-reasonml).
This example shows how powerful & helpful it can be to build a next js custom server with a typesafe language.
The example has been built off the `custom-server` example that uses pure `nodejs` to build the custom server.

View file

@ -0,0 +1,14 @@
{
"name": "custom-server-reason",
"version": "0.1.0",
"sources": {
"dir": "server",
"subdirs": true
},
"package-specs": {
"module": "commonjs",
"in-source": true
},
"suffix": ".bs.js",
"bs-dependencies": []
}

View file

@ -0,0 +1,26 @@
{
"name": "custom-server-reason",
"version": "0.1.0",
"scripts": {
"clean": "bsb -clean-world",
"build": "bsb -make-world",
"watch": "bsb -make-world -w",
"next-build": "next build",
"dev": "bsb -make-world && node server/server.bs.js",
"next:build": "bsb -make-world && next build",
"start": "NODE_ENV=production node server/server.bs.js"
},
"keywords": [
"BuckleScript"
],
"author": "",
"license": "MIT",
"devDependencies": {
"bs-platform": "^4.0.18"
},
"dependencies": {
"next": "^8.0.4",
"react": "^16.8.6",
"react-dom": "^16.8.6"
}
}

View file

@ -0,0 +1,3 @@
import React from 'react'
export default () => <div>a</div>

View file

@ -0,0 +1,3 @@
import React from 'react'
export default () => <div>b</div>

View file

@ -0,0 +1,17 @@
import React from 'react'
import Link from 'next/link'
export default () => (
<ul>
<li>
<Link href='/b' as='/a'>
<a>a</a>
</Link>
</li>
<li>
<Link href='/a' as='/b'>
<a>b</a>
</Link>
</li>
</ul>
)

View file

@ -0,0 +1,35 @@
type http;
[@bs.deriving abstract]
type parsedObjectUrl = {
pathname: string,
query: string,
};
module Request = {
type t;
[@bs.get] external url: t => string = "";
[@bs.get] external method_: t => string = "method";
};
module Response = {
type t;
[@bs.send.pipe: t] external end_: 'a => unit = "end";
[@bs.send] external setHeader: (t, string, string) => unit = "";
[@bs.send] external writeHead: (t, int) => unit = "";
let setHeader = (header: string, value: string, response: t) => {
setHeader(response, header, value);
response;
};
let writeHead = (status: int, response: t) => {
writeHead(response, status);
response;
};
};
[@bs.module "http"]
external createServer: ((Request.t, Response.t) => unit) => http = "";
[@bs.send.pipe: http] external listen: (int, string => unit) => unit = "";
[@bs.module "url"] external parse: (string, bool) => parsedObjectUrl = "";

View file

@ -0,0 +1,61 @@
open Http;
[@bs.deriving abstract]
type nextparams = {dev: bool};
type nextoutput;
[@bs.send] external prepare: nextoutput => Js.Promise.t(unit) = "";
[@bs.send]
external getRequestHandler:
(nextoutput, unit) => (. Request.t, Response.t, parsedObjectUrl) => unit =
"";
[@bs.send]
external render: (nextoutput, Request.t, Response.t, string, string) => unit =
"";
[@bs.module] external next: nextparams => nextoutput = "next";
[@bs.val] [@bs.scope ("process", "env")]
external port: Js.Nullable.t(int) = "PORT";
[@bs.val] [@bs.scope ("process", "env")]
external node_env: string = "NODE_ENV";
[@bs.val] external parseInt: (int, int) => int = "parseInt";
let port =
switch (Js.Nullable.toOption(port)) {
| Some(port) => port
| None => 3000
};
let is_dev = node_env != "production";
let nxt = nextparams(~dev=is_dev);
let app = next(nxt);
let handle = getRequestHandler(app, ());
Js.Promise.(
prepare(app)
|> then_(() => {
createServer((req, res) => {
let parsedUrl = parse(Request.url(req), true);
let pathname = pathnameGet(parsedUrl);
let query = queryGet(parsedUrl);
switch (pathname) {
| "/a" => app->render(req, res, "/a", query)
| "/b" => app->render(req, res, "/b", query)
| _ => handle(. req, res, parsedUrl)
};
})
|> listen(port, _err =>
print_string(
"> Listening on port http://localhost:" ++ string_of_int(port),
)
);
resolve(print_string(""));
})
);