Build a complete WebSocket server, using pure Node.js and JavaScript, from scratch.
Learn bitwise parsing.
Understand how to analyze a WebSocket frame (
No modules.
We use ONLY pure Node.js and pure JavaScript to benefit YOU most.
Don’t get hung up with large data. Step by step I'll show you how to create a WebSocket application that receives large data from a client, and sends it back over a WebSocket connection. So you learn how to to manage chunking, streaming, and fragmentation properly. You may not get this information elsewhere.
Build a complete WebSocket server, using pure Node.js and JavaScript, from scratch.
Learn bitwise parsing.
Understand how to analyze a WebSocket frame (
No modules.
We use ONLY pure Node.js and pure JavaScript to benefit YOU most.
Don’t get hung up with large data. Step by step I'll show you how to create a WebSocket application that receives large data from a client, and sends it back over a WebSocket connection. So you learn how to to manage chunking, streaming, and fragmentation properly. You may not get this information elsewhere.
Key Focus Areas
Pure Node.js and JavaScript: Empower yourself. Master the art of building a WebSocket server from scratch, leveraging only the core capabilities of Node.js and JavaScript.
Handling Large Payloads: Unlike other resources that focus on small to medium-sized payloads, this course will teach you how to manage chunking and fragmentation for large data transfers. This is a critical skill, as handling large payloads is a significant challenge that many developers shy away from.
Real-World Application: Develop an application that can efficiently handle large data payloads, ensuring your WebSocket server is robust, scalable, and reliable.
Such comprehensive and advanced course may not be available elsewhere. My course is dedicated to writing pure native server code that focuses on WebSockets. This course is particularly special because I show you how to handle sending and receiving large data payloads with WebSockets. . Most other courses or information on the web only focuses on small to medium sized payloads. That's relatively easy to do. So, as has become my trademark or characteristic of my courses, I tackle the hard practical stuff to elevate your learning to the next level so that you benefit most.
So the available information and courses which limit the WebSocket data payloads to 65,535 bytes is inadequate
This is not good enough for you.
Many modern applications, such as real-time analytics, file sharing, live updates, and multimedia streaming, involve transferring large amounts of data. If your WebSocket server cannot handle these large payloads, it may lead to performance issues, data loss, or even crashes.
Understanding these nuances is essential for building a robust server.
Buffering and Streaming: Large payloads often require efficient buffering and streaming mechanisms to prevent memory overflow and ensure smooth data transfer. Implementing these mechanisms correctly can be tricky.
Error Handling: Large payloads increase the likelihood of errors during transmission. A well-designed server must include error handling mechanisms to manage and recover from such errors gracefully.
My course is here to empower you, unashamedly.
Why Knowing How to Build a fully-functional WebSocket server is a Game Changer for Web App Developers
#1 Deep Understanding and Customization. Building a Node.js WebSocket server from scratch without using libraries is crucial for web developers because it provides a deep understanding of the underlying protocol and its mechanics. By implementing WebSockets purely in JavaScript, developers gain hands-on experience with the intricacies of real-time communication, including framing, chunking, working with readable streams, fragmentation, and error handling. This knowledge allows for better customization and optimization of the server to meet specific application needs, ensuring performance and scalability.
#2. Mastering Core Concepts and Flexibility. Learning to build a WebSocket server from scratch also enhances problem-solving skills and flexibility. Without the abstraction of libraries, developers must handle every detail, from establishing connections to managing state and handling errors. This approach fosters a comprehensive understanding of the technology, enabling developers to troubleshoot and optimize their applications more effectively. Additionally, it allows for the creation of tailored solutions that can be adapted to various use cases, making the developer more versatile and proficient in handling complex real-time applications.
You need to have a basic idea of what WebSockets are.
If you have never come across the term before, then I encourage you to take my WebSockets Protocol - Very Informative course. In that course, I take you through the WebSocket protocol from the basics through to the advanced.
A basic knowledge of
WHAT YOU'js WebSocket server from scratch, without relying on any libraries. You'll gain a deep understanding of the WebSocket protocol and master its implementation on the server side.
You'll learn and master the WebSocket protocol, without relying on libraries.
1. Creating the Initial HTTP Server:
Start by setting up an HTTP server, which will serve as the foundation for your WebSocket connection.
2. Implementing the Handshake Process:
Learn how to calculate the Sec-WebSocket-Accept value on the server and send back the required headers to upgrade the HTTP connection to a WebSocket connection.
3. Receiving WebSocket Data:
Understand how to listen for data events on the WebSocket connection.
Extract important information from WebSocket frames, including the FIN bit, opcode, and payload length fields.
Learn how to extract the Masking Key from the client and use it to decrypt the received data.
4. Sending WebSocket Data:
Unpack the structure of WebSocket data frames.
Construct and send binary frames to the client over the wire.
5. Handling Closure Frames and the Closing Handshake:
Learn how to process different types of WebSocket frames, including closure frames.
Understand how to extract closure codes and reasons on both the client and server.
Discover how to send a close frame to the client and gracefully close the underlying WebSocket connection in compliance with the RFC.
These concepts are also used in most other programming languages like PHP, Python, C, C++, Java, etc, setting you up for success.
Chunking and Fragmentation: Learn how to handle large payloads by chunking and fragmenting data in compliance with the WebSocket protocol.
Reading and Writing to Buffers: Understand how to efficiently read from and write to buffers.
SHA Hashing: Learn how to perform SHA-1 hashing for the WebSocket handshake.
Examining Data Packets: Gain insights into examining and processing data packets using bitwise operators.
Encrypting and Decrypting Data: Understand how to encrypt and decrypt data using the Masking Key and the modulus operator.
Why This Course Stands Out
The focus is on YOU. This course is uniquely dedicated to writing pure native server code for WebSockets, a topic often overlooked in favour of simpler, library-based solutions.
This course shows you how to deal with large WebSocket data payloads, you'll gain a deep understanding of the WebSocket protocol and the skills to tackle even the most demanding real-time applications
Meet Your Instructor: Clyde
Clyde is a coding enthusiast who has been immersed in the world of computers since the age of 7.
With years of experience in web development and a passion for teaching, Clyde brings a wealth of knowledge and practical insights to the course. His engaging teaching style and real-world examples will make complex concepts accessible and enjoyable.
Enrol now
Don’t miss this opportunity to elevate your web development skills by building a WebSocket server from scratch.
Web development is a blazing hot topic at the moment. But you have a distinct advantage. This course offers memorable learning topics, actionable tactics and real-world examples.
Get ready to transform your projects into interactive experiences that captivate users and make you money.
Let's get crackin'
Setting up a WebSocket connection involves two key parts: the HTTP handshake upgrade process and the data transfer process. The first part, the HTTP handshake, occurs when the client initiates a connection by sending an HTTP request with specific headers to the server, indicating a desire to upgrade to the WebSocket protocol. The server then responds with a confirmation, completing the handshake and establishing a WebSocket connection. The second part is the data transfer process, which allows for full-duplex communication between the client and server, enabling them to send and receive messages in real-time without the overhead of additional HTTP requests, thus facilitating efficient and interactive data exchange.
To build a WebSocket server that is compliant with standards, it is essential to adhere to the specifications outlined in the relevant RFCs, particularly RFC 6455, which defines the WebSocket Protocol. Adhering to this standard allows developers to create applications that can work across various platforms and browsers without compatibility issues.
This course follows a structure that maximizes your learning of advanced concepts of building a WebSocket server from scratch. You will gain a comprehensive understanding of both the handshake and data transfer processes involved in WebSocket communication. I’m excited to guide you through this journey and help you master the skills needed to build compliant and efficient WebSocket servers!
While other libraries like ws or the websocket Node module also implement WebSocket functionality, they may not provide the same level of adherence to the RFC standards or may focus on specific use cases without the comprehensive feature set provided by RFC 6455. For instance, while these libraries facilitate WebSocket communication, they might not encapsulate all the nuances of the protocol as outlined in the RFC, potentially leading to compatibility issues or security vulnerabilities if not implemented correctly.
Are you ready?
In this course, we will build a WebSocket server using Node.js, without relying on external libraries.
You learned about the significance of the WebSocket specification, which outlines the essential requirements and behaviors for WebSocket servers. This is important as we need to ensure our WebSocket server adheres to established standards.
As you complete the short quiz, you’ll reinforce your understanding before diving into the next section.
Get ready to roll up your sleeves, as we will soon build the HTTP server that will serve as the foundation for your WebSocket implementation!
See you soon.
To establish a WebSocket connection, it is essential to have an existing HTTP server that is already established and running. This requirement applies to all versions of HTTP, including HTTP/1.1, HTTP/2, and HTTP/3.
I have attached the starting HTML and CSS files to this lecture.
I have added some extra comments to make more sense of it.
This is the starting file for the client-side code.
In this index.html file, we set up the form and buttons that allow a user to open up a WS connection, submit data, and also close a WebSocket connection.
As you will recall (from doing my WebSockets fundamentals course) you'll know that there are 4 events on the WebSocket API and 2 methods.
I will attach the HTML and CSS starting files to the next lecture so you can download the starting client-side code.
We will now use Node's inbuilt native 'http' module to create a HTTP server. This will form the entry point for our WebSocket server.
We will define our PORT variable (and a bunch of others throughout this course) in a separate module. This makes for cleaner and better code.
In this lecture I want to add a CUSTOM_ERRORS error to our modules folder, and then use JavaScript's forEach loop to add each custom error event to our process object in node.
Visual Studio Code (VSCode) provides built-in support for running and debugging Node.js applications, including servers and WebSocket APIs.
Before I end this section, I want to test that our error handling code is working as expected. You'll learn that the SIGINT is not really an "error" in the true strict sense of the word, and I'll show you that our custom event handler function does indeed work.
I'll finally also explain a little more about what process.exit() is and why I passed an exit code of "1" into the function.
I have attached the final code for this section.
Nice!
You've successfully established a web server using Node's native http module. Here are some key achievements so far:
Basic Error Handling: You’ve implemented foundational error handling on Node's process object, which you can expand upon as you continue to develop your skills.
Project Structure: You’ve organized your project files effectively, creating an app.js file that serves as the entry point for your WebSocket server.
Modular Code: By creating custom libraries and exporting modules, you’ve laid the groundwork for cleaner and more maintainable code.
What’s Next?
In the upcoming section, we will mount our WebSocket server on top of this HTTP server and upgrade the request.
Keep up the great work!
When a client wishes to establish a WebSocket connection, it initiates the process by sending an HTTP request to the server. This request includes specific headers that indicate the client's intent to upgrade the connection to WebSocket.
Whenever an 'upgrade' event is fired, Node.js will fire the 'upgrade' event listener and in doing so will provide us with 3 important objects:
the request object,
the socket object
the head buffer
As you'll see, when a request is received from the client, it is of type IncomingMessage. If we examine this object in Node's terminal, you'll notice that there is a rawHeaders property but not a plain "headers" property. I will explain why this is the case in this lecture.
In this lecture we will look at the req.headers object to examine and perform checks on the client's upgrade request. It needs to be in conformance with the RFC 6455, which means we will have to define some custom error handling.
Rather than kill our server if there is an error in the client handshake headers, let's make sure our code is in compliance with the RFC by sending back an appropriate HTTP/1.1 response.
In this lecture we will start to generate the required headers that the server needs to send back to the client in order to accept a valid WebSocket protocol request.
As you know, one required header that the server needs to respond back with is the Sec-WebSocket-Accept header. The value of this header is a 3 step-process. The first step is to concatenate (fancy dev word for "join") the client Sec-WebSocket-Key with the GUID. In this lecture I want to set up functions to handle this.
In order to calculate the Sec-WebSocket-Accept value, we are required by the WebSocket protocol to perform a SHA1 hash and encode it using base64.
I want to finish generating the Sec-WebSocket-Accept value in this lecture, and send back the appropriate headers to the client. If everything was done successfully at this point, we have a valid WebSocket connection.
In this lecture we will end part 1 of this course - that is, writing native code that establishes a WebSocket connection with a client.
Remember, even though we have a valid WebSocket connection, we have not written any server-code that can handle messages. This is a topic we will cover the remainder of the course.
I can't wait.
Here is a summary of the upgrade process.
Here is the final project code up to this point.
Bravo. You’ve just unlocked a key milestone in your journey!
You’ve successfully established a WebSocket connection with a client, and that’s no small feat!
In this section, you dove deep into the intricacies of the 'upgrade' event listener, unraveling the 3 objects returned to you during this process.
You’ve learned how to scrutinize the request object and extract headers, ensuring they meet the rigorous standards of the WebSocket protocol.
And finally, you’ve harnessed the power of the crypto module to generate the Sec-WebSocket-Accept value.
What’s Next?
This marks the completion of the first part of the course. In Part 2 of this course, we’ll dive into the advanced aspect of receiving WebSocket data messages from a client.
Let’s keep the momentum going!
The next goal I want you to achieve is being able to parse and receive incoming data sent from a client.
I want to now create a app_v3 file that we can use for our next milestone.
In Node.js, when handling WebSocket connections using the HTTP module, the 'upgrade' event is emitted when the server receives an HTTP request with an Upgrade header. This event provides us with a "socket" object that is crucial for establishing the WebSocket connection.
In this lecture you will learn:
The socket object passed to the 'upgrade' event handler is an instance of the net.Socket class, which is part of the net module.
net.Socket inherits from the stream.Duplex class, which means it implements both the readable and writable interfaces of a stream.
By inheriting from stream.Duplex, the net.Socket object can be used for both reading and writing data simultaneously, enabling full-duplex communication required for WebSocket connections.
In Node.js, the socket object refers to an instance of the net.Socket class, which is part of the net module.
Now that we are ready to go, let's listen for the 'data' event and define what happens in our callback function. As you'll learn, I want to define a receiver object that we can keep track of all the chunks of data received. Although we won't strictly need this now, we want to set up our code so that we can later receive large amounts of data and WebSocket fragments.
In Node.js, when using the socket object in the context of WebSocket connections (particularly during the 'upgrade' event), you can listen for several events that are emitted by this socket. The socket object, which is an instance of net.Socket, inherits from the EventEmitter class, allowing it to emit and respond to various events.
The end event is emitted when the other end of the socket has finished sending data. It indicates that the connection has been closed from the remote side.
In this lecture let's start creating our receiver object. I will use JavaScript's class syntax to define our factory function, even though I don't have to. In fact, it'll be a great exercise for you to refactor the code at the end of this course to work with pure native function constructors in JavaScript.
In order to write awesome code, its best to sit back and visualize what it is we're working with and what we are trying to achieve.
In this lecture I want to show you the various tasks that will be involved in extracting payload data from a WebSocket data frame.
As you'll see, there are effectively four steps to extracting data from a WS frame:
get frame header information in the first 2 bytes
get the payload length indicator
get the masking key
finally, get the actual payload data.
This will be managed by our looping engine.
In this lecture I want to code up the looping function that will be responsible for handling and directing our node.js process to execute the correct task.
By the end of this lecture you would have extracted the first 2 bytes of data from the Websocket frame. Pretty cool, huh?
Well done for getting this far.
When dealing with WebSockets, buffers are essential for parsing and constructing WebSocket frames, which are binary structures containing headers and payload data. For example, when receiving a WebSocket message, you use buffers to extract the frame headers, payload length, and masking key, and then to unmask the payload data using XOR operations.
Working with buffers is essential for managing the low-level details of WebSocket communication, ensuring that data is correctly parsed and transmitted between the client and server.
Before we get back to code, I want to illustrate how the letter "a" is sent over the wire. Did you know that it will take up 7 bytes of data? I will show you how this is broken up as a WebSocket data frame.
I want to take this time to open the debugger tool and analyze what we've received so far.
Its time to start extracting header information from our WebSocket frame. In this lecture I'll show you how to use Bitwise operators to extract the first bit (the FIN bit) from our first byte of data.
To extract WebSocket frame header information using native Node.js without any external libraries, you need to understand the structure of a WebSocket frame and use bitwise operators to decode the header.
Know that you know how Bitwise parsing works, let's use the & operator to extract the data we need.
You've completed the first task of this section - that is, extracting the first 2 crucial bytes from a WebSocket frame and have extracted the FIN, MASK, OPCODE and initial LENGTH fields.
The next step is to calculate the actual length of the payload.
Well done for extracting the first 2 bytes of information from a WS frame. The next step is to now calculate the exact size of the payload data contained within the first fragmented message.
The RFC defines 3 categories of payload length sizes, and they have set appropriate flags in order for us to figure that out.
The WebSocket protocol organizes message sizes into three distinct categories to optimize data transmission. Small messages, under 126 bytes, have their length embedded in the initial two bytes of the frame header. Medium messages, between 126 and 65,535 bytes, need two additional bytes to denote their size. Large messages, exceeding 65,535 bytes, use eight extra bytes to specify their length, enabling support for messages up to 2^63-1 bytes. This method ensures that smaller messages are handled with minimal overhead, while larger messages are accurately represented.
Calculating the payload length of a WebSocket frame is not trivial due to the complexity introduced by varying frame header sizes, which depend on the payload size. In this lecture let's implement our own logic to calculate the exact payload length of our websocket frame.
The _getLength method is essential for calculating and returning the _framePayloadLength property, which is critical for correct frame parsing, resource management, performance optimization, and compliance with the WebSocket protocol. This property provides a unified representation of the payload size across all scenarios, ensuring that the server can handle frames of varying sizes efficiently and reliably.
A quick summary and recap of how the _getLength() method works.
In the previous lecture I used the readUInt16BE() method. I want to take a break and explain to you what big endian means.
I want to define our processLength() function in this lecture, where we perform some basic length checks.
You are almost done. You've extracted the first 2 bytes of the WS frame. You've also extracted all frame header info necessary to calculate the length of the payload.
The last step before extracting payload data is to consume the masking key.
Good luck!
As you know, before we can consume the payload, we need to ensure that we have decoded or "unmasked" the payload. To do this, the first step is to extract the masking key that is part of the websocket frame.
Well done on getting this far. In this lecture we will start focusing on the final task that we need to complete in order to finish this section - extracting the actual payload.
Let's start working on our getPayload() method.
When we defined the getPayload() function, the first thing we had to do was check whether we have enough data in our custom buffer variable to consume a full frame of data.
If we don't have enough data, then we have to jump out of the receiver object and wait for another chunk of data to be received on the socket object.
I want to loop through our _buffersArray and extract the amount of chunks necessary to make up one full WebSocket frame.
You may be wondering why I have decided to create a new function called _consumePayload() when we have already defined a function called _consumeHeaders() to extract and consume bytes from a ws frame.
Well, let me explain why in this lecture.
In this lecture we will using the client's masking key, and the XOR operator to unmask the payload data.
The index variable keeps track of the current position in the payload data. The % 4 operation ensures that the index wraps around to 0 after reaching 3, thus cycling through the 4-byte masking key.
In this lecture I want to create a persistent fragments array and check for more frames (FIN bit check)
Having a fragments array helps us keep a persistent array of all frames received from the client.
In this lecture I want to use the Debugger tool to examine our code and logic for small payloads.
In the previous lecture we encountered an error where we did not update our total bytes read. I will fix it here, and continue to analyze our logic and code.
When we want to test medium and large payload sizes, I want an easy way for us to do so. Manually copy/pasting large blocks of text is tedious. That's why in this lecture I want to create a button that we can click on that does this for us.
I want to examine the case where the client sends 50,000 bytes of data to the server.
All of our code logic related to medium sized payloads (i.e. where our _initialPayloadSizeIndicator variable is set to 126) should execute.
The values 195 and 80 in your buffer are byte values or unsigned 8-bit integers, represented in decimal form.
Binary and decimal are different number systems. Binary uses base 2 (0s and 1s), while decimal uses base 10 (0 through 9).
That's why I had to call the readUInt16BE() method - to represent our binary data into number format.
For the first time in this course, we can finally test our code.
I want to test the scenario where the client sends 150,000 bytes of data to the server in one WebSocket message.
As you'll see:
the client packages this WebSocket message into 2 fragments
the first frame payload size sent is 131,000 bytes
due to network constraints, the first chunk received is only 65k bytes in size. This means we don't even have enough data yet to consume our first frame.
In summary, BigInt is a new data type in JavaScript that allows for the representation of very large integers with arbitrary precision, solving the limitations of the standard Number type. The n suffix is used to denote a BigInt value, ensuring that it is treated differently from a standard Number.
In short, chunks are network segments, frames are WebSocket-specific, and messages can be made up of multiple frames. This helps in handling large data efficiently over the network.
I want to now pick up exactly where we left off in the previous lecture. As you know, we have only received a chunk of 65k bytes, but our full frame payload size is 131,000 bytes.
This means we have to test our code to ensure that we jump out of the receiver object and wait for additional chunks of data to be fired on our socket object.
Let's finish off with the debugger session on our large payload. In this lecture you'll see how we examine the FIN bit and receive the last frame of data and how we combine everything into our _fragments array.
A quick lecture to add some opcode checks and improve some comment descriptions.
This is an advanced topic, but as I keep saying, I want you to become a Grandmaster at WebSockets.
I want to show you via code and a practical example how the debugger vs running node.js live can produce different chunk sizes.
If you will recall from the previous lecture, we had 58 bytes in our _buffersArray and we then use the _getInfo() method to consume the first 2 bytes of header information.
BUT what happens if by chance, we are only left with 1 byte of data? or even 0 bytes? In other words, what happens if we don't have enough data to consume the first 2 bytes of header info?
We will get thrown an error.
So in this lecture let's just fix that quickly, by telling our engine to wait for an additional data chunk.
Well done for completing this section of the course. It has not been easy, but I'm confident you've learnt a TON. Attached are the coding files we've done thus far.
You've just achieved something truly remarkable! This section has been an epic journey, and your progress is nothing short of amazing.
What You've Accomplished
Receiver: You've successfully created a receiver object that can handle chunks of data sent from a WebSocket client.
Worked with buffers: you've had to create various buffers to receive data, ultimately inserting a full WS frame into a fragments buffer.
Frame Decoding: You extracted the first 2 bytes of the WebSocket frame to uncover crucial information about the data.
Payload Length Headers: You've navigated the payload length headers to determine the actual size of the payload data.
Masking Key Consumption: You've tackled XOR operations to decipher the actual payload.
Enjoy the quiz I put together for you, and I'll see you in the next section.
I want to create a new version app file so that we can start concentrating on writing code for our _sendEcho() function.
The first part of our mission is to correctly allocate enough space in our frame buffer to represent the entire websocket message.
In WebSocket communication, the header of a frame includes a variable-length field to indicate the payload length. This field is necessary because the payload can vary significantly in size, and a fixed-length header would be inefficient. The variable-length header allows for compact representation of small payloads while accommodating larger ones without excessive overhead.To calculate the size of this variable-length header, you start with the minimum 2 bytes that include the FIN and opcode byte, and the mask and length indicator byte. If the payload length is between 0 and 125, this 2-byte header is sufficient. However, if the payload length is between 126 and 65535, an additional 2 bytes are added to represent the 16-bit length. For payloads larger than 65535 bytes, an additional 8 bytes are used to represent the 64-bit length. For client-initiated frames, an extra 4 bytes for the masking key are also included. This dynamic approach ensures efficient use of bandwidth while supporting a wide range of payload sizes.
The payloads in the WebSocket frame header are variable in size to efficiently accommodate a wide range of payload lengths. This approach allows for compact representation of small payloads, using just the initial 2 bytes of the header for lengths up to 125 bytes. For larger payloads, additional bytes are used to represent the length: 2 bytes for lengths between 126 and 65535 bytes, and 8 bytes for lengths greater than 65535 bytes. This variable-length encoding ensures that the overhead is minimized for small messages while still supporting large payloads, making the protocol flexible and efficient
You now have all the required information necessary (the 2 bytes, plus any additional bytes required to represent the payload length) to construct the blueprint of the frame that our server will send back to the client.
I am now going to create the first byte of the binary frame. This means we have to correctly set the FIN, RSV bits and OPCODE.
When a server sends text data over a WebSocket connection, the binaryType property does not affect how the text data is received. The binaryType property only determines how binary data is represented on the client side.
Bitwise shift operators are low-level operators that manipulate the individual bits of an integer by shifting them to the left or right.
We have all the information required to now populate our frame header with data.
In this lecture we will access our socket object (that represents our websocket connection) and send the frame to the client using the write() method.
I want to test whether our socket.write(frame) method has worked as expected. I am sure you will be pleasantly surprised.
A Blob in the context of web APIs and a Buffer in Node.js share some similarities but also have distinct differences.
The socket.write() method for WebSockets requires the data to be in the form of a Buffer. This is because WebSockets use a specific binary frame format that includes headers and payload, which must be constructed manually. You need to manually construct the WebSocket frame according to the WebSocket protocol specification. This involves creating a Buffer that includes the necessary headers and the payload.
OpenCourser helps millions of learners each year. People visit us to learn workspace skills, ace their exams, and nurture their curiosity.
Our extensive catalog contains over 50,000 courses and twice as many books. Browse by search, by topic, or even by career interests. We'll match you to the right resources quickly.
Find this site helpful? Tell a friend about us.
We're supported by our community of learners. When you purchase or subscribe to courses and programs or purchase books, we may earn a commission from our partners.
Your purchases help us maintain our catalog and keep our servers humming without ads.
Thank you for supporting OpenCourser.