Dynamic Static Typing In TypeScript
JavaScript is an inherently dynamic programming language. We as developers can express a lot with little effort, and the language and its runtime figure out what we intended to do. This is what makes JavaScript so popular for beginners, and which makes experienced developers productive! There is a caveat, though: We need to be alert! Mistakes, typos, correct program behavior: A lot of that happens in our heads!
Take a look at the following example.
app.get("/api/users/:userID", function(req, res) {
if (req.method === "POST") {
res.status(20).send({
message: "Got you, user " + req.params.userId
});
}
})
We have an https://expressjs.com/-style server that allows us to define a route (or path), and executes a callback if the URL is requested.
The callback takes two arguments:
- The
request
object. Here we get information on the HTTP method used (e.g GET, POST, PUT, DELETE), and additional parameters that come in. In this exampleuserID
should be mapped to a parameteruserID
that, well, contains the user’s ID! - The
response
orreply
object. Here we want to prepare a proper response from the server to the client. We want to send correct status codes (methodstatus
) and send JSON output over the wire.
What we see in this example is heavily simplified, but gives a good idea what we are up to. The example above is also riddled with errors! Have a look:
app.get("/api/users/:userID", function(req, res) {
if (req.method === "POST") { /* Error 1 */
res.status(20).send({ /* Error 2 */
message: "Welcome, user " + req.params.userId /* Error 3 */
});
}
})
Oh wow! Three lines of implementation code, and three errors? What has happened?
- The first error is nuanced. While we tell our app that we want to listen to GET requests (hence
app.get
), we only do something if the request method is POST. At this particular point in our application,req.method
can’t be POST. So we would never send any response, which might lead to unexpected timeouts. - Great that we explicitly send a status code!
20
isn’t a valid status code, though. Clients might not understand what’s happening here. - This is the response we want to send back. We access the parsed arguments but have a mean typo. It’s
userID
notuserId
. All our users would be greeted with “Welcome, user undefined!”. Something you definitely have seen in the wild!
And things like that happen! Especially in JavaScript. We gain expressiveness – not once did we have to bother about types – but have to pay close attention to what we’re doing.
This is also where JavaScript gets a lot of backlash from programmers who aren’t used to dynamic programming languages. They usually have compilers pointing them to possible problems and catching errors upfront. They might come off as snooty when they frown upon the amount of extra work you have to do in your head to make sure everything works right. They might even tell you that JavaScript has no types. Which is not true.
Anders Hejlsberg, the lead architect of TypeScript, said in his MS Build 2017 keynote that “it’s not that JavaScript has no type system. There is just no way of formalizing it”.
And this is TypeScript’s main purpose. TypeScript wants to understand your JavaScript code better than you do. And where TypeScript can’t figure out what you mean, you can assist by providing extra type information.
Basic Typing
And this is what we’re going to do right now. Let’s take the get
method from our Express-style server and add enough type information so we can exclude as many categories of errors as possible.
We start with some basic type information. We have an app
object that points to a get
function. The get
function takes path
, which is a string, and a callback.
const app = {
get, /* post, put, delete, ... to come! */
};
function get(path: string, callback: CallbackFn) {
// to be implemented --> not important right now
}
While string
is a basic, so-called primitive type, CallbackFn
is a compound type that we have to explicitly define.
CallbackFn
is a function type that takes two arguments:
req
, which is of typeServerRequest
reply
which is of typeServerReply
CallbackFn
returns void
.
type CallbackFn = (req: ServerRequest, reply: ServerReply) => void;
ServerRequest
is a pretty complex object in most frameworks. We do a simplified version for demonstration purposes. We pass in a method
string, for "GET"
, "POST"
, "PUT"
, "DELETE"
, etc. It also has a params
record. Records are objects that associate a set of keys with a set of properties. For now, we want to allow for every string
key to be mapped to a string
property. We refactor this one later.
type ServerRequest = {
method: string;
params: Record<string, string>;
};
For ServerReply
, we lay out some functions, knowing that a real ServerReply
object has much more. A send
function takes an optional argument with the data we want to send. And we have the possibility to set a status code with the status
function.
type ServerReply = {
send: (obj?: any) => void;
status: (statusCode: number) => ServerReply;
};
That’s already something, and we can rule out a couple of errors:
app.get("/api/users/:userID", function(req, res) {
if(req.method === 2) {
// ^^^^^^^^^^^^^^^^^
Advertising by Adpathway