I feel almost ashamed to be speaking about cookies in a modern web development book in 2015, but they are the oldest, and most stable, form of client-side storage available to developers today. They are certainly not the best method, and I’d almost never recommend using them, but they are an option and you may be forced to use (or modify) code that makes use of them at some point in the future.
Cookies were introduced in 1994 in a beta version of Netscape. They worked by using header values sent along with HTTP requests and responses. As you may know, whenever your browser requests a resource, a set of headers will be sent along with the request. Those headers include various types of data, including information about the browser and what form of data it wants. On the flip side, the server will also send headers back. Basically, every time you see a web page rendered in your browser, there was a set of headers that were also sent that you don’t see. (You certainly can see them using browser tools. They aren’t hidden as in “impossible to see,” just hidden from view by default.)
Cookies are sent using HTTP headers, specifically the “Cookie” HTTP header, and are sent by the browser to the server and sent to the browser from the server. Right away you should see a problem with that. If one of the benefits of using client-side storage is that we don’t have to send data over the wire, doesn’t sending cookies back and forth negate that benefit? Absolutely. This is one more reason why I typically won’t recommend using cookies.
By default, the browser makes no limit on the number of cookies it can have. In the past, there was a limit of 20 cookies per domain, but modern browsers seem to have removed that limit. (As an aside, I once set over 400 cookies in a Chrome browser and it worked just fine. However, my web server started throwing errors when requests were made. So in this case, it was an issue of the web server having a limit, not the browser itself. But please don’t use 400 cookies.) Research, or in other words, Googling, seems to indicate that a limit of 50 cookies per domain is safe at a total of 4 KB. That isn’t a lot of space for cookie values, which hinders their real-world usage.
Cookies are unique per domain. This means that a cookie value set on foo.com will not be available on goo.com. This is good since you wouldn’t want some other site to interfere with your usage on your own site. Cookies can be made to be unique in subdomains as well. So, for example, maybe app.foo.com is a unique subdomain of the Foo website. You can create cookies that are readable only on app.foo.com as well as cookies that would be available to www.foo.com and app.foo.com.
To make things even more complex, you can also create cookies that are valid only for a particular path. So, instead of app.foo.com, you may want to create cookies available just to foo.com/app.
Finally, you can create cookies that work only on the secure (https) version of your site. Obviously which of these options you use will depend on whatever your application is doing and where you think cookie values need to be present.
Along with where cookies are available, you can also specify how long they are available. Your options are:
Cookies do not have an API. To work with them, you simply access the document.cookie
object in your code. So for example, to create a cookie, you could do this:
document
.
cookie
=
"nameOfCookie=value"
;
In the preceding example, I created a cookie called nameOfCookie
and defined it as value. You use a "name=value"
format to define the name and value all at once. Here is a real example:
document
.
cookie
=
"name=Raymond"
;
In the previous example, I simply set a cookie called name
to the value Raymond
. Values must be URL-safe, which means if you are doing anything dynamic, you’ll want to use a helper function like encodeURIComponent
.
name
=
"Raymond Camden"
;
document
.
cookie
=
"name="
+
encodeURIComponent
(
name
);
So far, so good. But here’s where things get a bit wonky. You may be wondering how you would set multiple cookies. If you literally set document.cookie
multiple times, it just works. So, for example:
document
.
cookie
=
"name=Raymond"
;
document
.
cookie
=
"age=43"
;
In this code sample, we’ve actually created two cookies, not one. That just feels plain wrong to me, but you’ll have to just roll with it.
So that’s how you create a cookie with a value, but what about all the metadata I mentioned—like defining where a cookie is available and how long it lasts? The format for appending metadata in a cookie is to use a semicolon after the value. Here is an example:
document
.
cookie
=
"name=Raymond; expires=Fri, 31 Dec 9999 23:59:59 GMT"
;
This example specifies when the cookie expires. We can further expand it by specifying that it works only on a subdomain:
document
.
cookie
=
"name=Raymond; expires=Fri, 31 Dec 9999 23:59:59 GMT;
domain=app.foo.com"
;
You get the idea. When you don’t specify metadata like this, cookies will default to the current domain, the current path (probably not what you want), and an expiration in the current session.
Reading cookies is somewhat easier—depending on how comfortable you feel about string parsing. There is no API to get “a” cookie. Instead, you can simply read document.cookie
. Doing so will give you all the cookies set for a particular site. Here is the document.cookie
value from CNN:
"_cb_ls=1; _chartbeat2=Dlxk2YDHxyg1BXCry6.1426601000831.1439508384927.0000000000000001; Akamai_AnalyticsMetrics_clientId=89E881222E0BD593DF2468758F328F689C36BAC1; octowebstatid=16ppgnhrso5f2frjuvq5; ug=55cd27810eb00b0a3c6ac33c7d05339d; ugs=1; __CG=u%3A2449373858398994400%2Cs%3A72001958%2Ct%3A1439508379253%2Cc%3A1%2Ck%3 Awww.cnn.com/19/19/54%2Cf%3A0%2Ci%3A0; __CT_Data=gpv=10; __gads=ID=3d001e8bba3c7c6d:T=1426601001:S=ALNI_MYWNYv1SRt0tx7LQ2AzdSESOBygNA; __vrf=1439508379290061VnNeHVWPiIjkcWMeUjRWpppwsPktE; grvinsights=a5a942f8e7c604d573496053d63f590c; optimizelyBuckets=%7B%7D; optimizelyEndUserId=oeu1426600996913r0.5135214943438768; optimizelySegments=%7B%22170962340%22%3A%22false%22%2C%22171657961%22%3A%22 safari%22%2C%22172148679%22%3A%22none%22%2C%22172265329%22%3A%22direct%22%7D; RT=sl=1&ss=1439508375405&tt=9387&obo=0&bcn=%2F%2F36f11e49.mpstat.us%2F&sh=1439 508384794%3D1%3A0%3A9387&dm=cnn.com&si=5be398d8-bb51-42ea-8128-6d4251e47ada; s_cc=true;s_fid=5324AC5D0F8323AB-3F26DEB602CBB276; s_ppv=13; s_sq=%5B %5BB%5D%5D;s_vi=[CS]v1|2A841A13051D0B97-400001280000490C[CE]; tosAgreed=true"
If that seems like a mess, you’re absolutely correct. Reading an individual cookie would mean parsing the string into components separated by a semicolon. Also note that you do not have access to any metadata. There is no way to get this information via the document.cookie
value. It wouldn’t be too difficult to parse the string, but you really don’t need to do that. Toward the end of this chapter I’ll show you a great little library that makes working with cookies easier.
Now that you’ve seen the basics of working with cookies, let’s look at a simple demo. As I mentioned, you probably don’t want to build cookie parsing code yourself. Instead, you should use one of the many libraries out there. For our demo, we’ll use a simple (and excellent) free library from the Mozilla Developer Network (MDN). You can find this code (along with even more information on cookies) at https://developer.mozilla.org/en-US/docs/Web/API/Document/cookie. The library has methods for:
getItem
setItem
removeItem
hasItem
keys
The MDN code has been saved in a file called cookies.js. This is available along with the rest of the code for this book. Our first example, test1.html, will simply use a cookie to count how many times you’ve visited the site (Example 2-1).
As a quick aside, all examples in this book will assume (and require) that you run them from a local web server and not just by opening the file in your browser. If you don’t have a local web server installed, consider a simple tool like httpster for a quick way to set up a development web server. (But promise me you’ll install a proper web server later. You are a web developer, right?)
<!DOCTYPE html>
<html>
<head>
<meta
charset=
"utf-8"
>
<title>
Cookie Test One</title>
<meta
name=
"description"
content=
""
>
<meta
name=
"viewport"
content=
"width=device-width"
>
<script
type=
"text/javascript"
src =
"http://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"
></script>
<script
type=
"text/javascript"
src=
"cookies.js"
></script>
</head>
<body>
<div
id=
"resultDiv"
></div>
<script>
$
(
document
).
ready
(
function
()
{
//initial value
var
visited
=
0
;
//Check to see if we have the cookie...
if
(
docCookies
.
hasItem
(
"visited"
))
{
//and get it
visited
=
docCookies
.
getItem
(
"visited"
);
}
visited
++
;
//update
docCookies
.
setItem
(
"visited"
,
visited
);
$
(
"#resultDiv"
).
text
(
"You have visited this site "
+
visited
+
" times."
);
});
</script>
</body>
</html>
Starting from the top, you can see a fairly typical document.ready
block from the friendly jQuery library. I begin by setting an initial value for a variable called visited
. If the cookie library says I have a cookie called visited
, I then update the variable with the cookie’s value. I then increment this value by one, store it back, and then display it in the browser. By default, this cookie will exist only for the current session, so let’s improve it in test2.html (Example 2-2).
<!DOCTYPE html>
<html>
<head>
<meta
charset=
"utf-8"
>
<title>
Cookie Test Two</title>
<meta
name=
"description"
content=
""
>
<meta
name=
"viewport"
content=
"width=device-width"
>
<script
type=
"text/javascript"
src =
"http://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"
></script>
<script
type=
"text/javascript"
src=
"cookies.js"
></script>
</head>
<body>
<div
id=
"resultDiv"
></div>
<script>
$
(
document
).
ready
(
function
()
{
//initial value
var
visited
=
0
;
//Check to see if we have the cookie...
if
(
docCookies
.
hasItem
(
"visited2"
))
{
//and get it
visited
=
docCookies
.
getItem
(
"visited2"
);
}
visited
++
;
//update
docCookies
.
setItem
(
"visited2"
,
visited
,
Infinity
);
$
(
"#resultDiv"
).
text
(
"You have visited this site "
+
visited
+
" times."
);
});
</script>
</body>
</html>
In this version, we’ve made two changes. First, our cookie is now visited2
. Normally I’d use the same name as before, but we want to differentiate between the two cookies while testing. The second change was to modify the setItem
call. We’ve used an expiration value of Infinity
. This creates a cookie that will last forever. Now that page will accurately reflect how many times it has been visited by a particular user regardless of whether the user is visiting during the same browser session.
So far our demos have been pretty simple, so let’s take it up a notch. We can use cookies to remember when a user has last visited the site. Based on the user’s last visit, we can either:
Greet a new user who has never been here before.
Provide a special message to someone who hasn’t been here in a while—for example, mentioning cool new features.
Simply welcome a user who has visited often.
Let’s look at the code (Example 2-3) and then I’ll walk you through how it works.
<!DOCTYPE html>
<html>
<head>
<meta
charset=
"utf-8"
>
<title>
Cookie Test Three</title>
<meta
name=
"description"
content=
""
>
<meta
name=
"viewport"
content=
"width=device-width"
>
<script
type=
"text/javascript"
src =
"http://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"
></script>
<script
type=
"text/javascript"
src=
"cookies.js"
></script>
</head>
<body>
<div
id=
"resultDiv"
></div>
<script>
$
(
document
).
ready
(
function
()
{
var
$resultDiv
=
$
(
"#resultDiv"
);
//Is this a brand new user?
var
newUser
=
true
;
//How many days since last visit
var
daysSinceLastVisit
;
//Check to see if we have the cookie...
if
(
docCookies
.
hasItem
(
"lastVisit"
))
{
newUser
=
false
;
//do some math to find out how long it has been
var
lastVisit
=
docCookies
.
getItem
(
"lastVisit"
);
var
now
=
new
Date
();
var
lastVisitDate
=
new
Date
(
lastVisit
);
//credit: http://stackoverflow.com/a/3224854/52160
var
timeDiff
=
Math
.
abs
(
now
.
getTime
()
-
lastVisitDate
.
getTime
());
var
daysSinceLastVisit
=
Math
.
ceil
(
timeDiff
/
(
1000
*
3600
*
24
));
}
//Set to now
docCookies
.
setItem
(
"lastVisit"
,
new
Date
(),
Infinity
);
if
(
newUser
)
{
$resultDiv
.
text
(
"Welcome to the site!"
);
}
else
if
(
daysSinceLastVisit
>
20
)
{
$resultDiv
.
text
(
"Welcome back to the site!"
);
}
else
{
$resultDiv
.
text
(
"Welcome good user!"
);
}
});
</script>
</body>
</html>
We begin by setting up two variables, newUser
and daysSinceLastVisit
. The first will simply be a Boolean we can check to determine if the user is brand new, while the latter will report on how many days it has been since the user’s last visit.
If a cookie, lastVisit
, exists, we get it and then create a Date
variable out of the value. From that we can then use some simple math to figure out how many days it has been since the user’s last visit.
Next, we set the cookie value for lastVisit
to the current time.
That’s the core of the logic; we then simply spit out a message based on one of the three states just mentioned. In this demo, the value 20
is used to determine that it has been too long since the user visited. Obviously this value is arbitrary.
Modern browsers provide excellent developer tools that make it easier to inspect and check your use of cookies. In Firefox, you can see cookies if you enable the Storage tab (it may not be visible by default). Once it’s enabled, you can view your current cookies (see Figure 2-1). Firefox does not allow you to modify cookies, just view them.
Chrome will show you a site’s current cookies in the Resources tab (Figure 2-2). You can delete cookies here, but not edit them.
CanIUse.com, the best resource for checking browser feature support, doesn’t even report on cookies because support is and has been 100% for a long time. However, just because a browser supports the feature doesn’t guarantee that it will work. Many people have developed a fear of cookies and blocked them.
As for recommended uses, as I said in the beginning, my recommendation is to not use cookies, but if you must, keep it simple. You can use them for user preferences and basic information (name, age, etc.). Here is a practical example. I use WordPress for my blog. Whenever I upload an image, WordPress asks if I want to include a link to the image when it’s added to the blog entry. I nearly always change this particular form field to say that I want no link. A cookie could be used to remember this default so I don’t have to constantly modify this when writing. That’s a trivial example, but it is something I run into nearly every day. Finally, if there is something you think the server should know as well, then using cookies ensures the server will see the same values.
52.14.240.252